sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True or 'always': Always quote. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.ConvertToCharset: lambda self, e: self.func( 135 "CONVERT", e.this, e.args["dest"], e.args.get("source") 136 ), 137 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 138 exp.CredentialsProperty: lambda self, 139 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 140 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 141 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 142 exp.DynamicProperty: lambda *_: "DYNAMIC", 143 exp.EmptyProperty: lambda *_: "EMPTY", 144 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 145 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 146 exp.EphemeralColumnConstraint: lambda self, 147 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 148 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 149 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 150 exp.Except: lambda self, e: self.set_operations(e), 151 exp.ExternalProperty: lambda *_: "EXTERNAL", 152 exp.Floor: lambda self, e: self.ceil_floor(e), 153 exp.Get: lambda self, e: self.get_put_sql(e), 154 exp.GlobalProperty: lambda *_: "GLOBAL", 155 exp.HeapProperty: lambda *_: "HEAP", 156 exp.IcebergProperty: lambda *_: "ICEBERG", 157 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 158 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 159 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 160 exp.Intersect: lambda self, e: self.set_operations(e), 161 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 162 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 163 exp.LanguageProperty: lambda self, e: self.naked_property(e), 164 exp.LocationProperty: lambda self, e: self.naked_property(e), 165 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 166 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 167 exp.NonClusteredColumnConstraint: lambda self, 168 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 169 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 170 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 171 exp.OnCommitProperty: lambda _, 172 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 173 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 174 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 175 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 176 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 177 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 178 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 179 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 180 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 181 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 182 exp.ProjectionPolicyColumnConstraint: lambda self, 183 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 184 exp.Put: lambda self, e: self.get_put_sql(e), 185 exp.RemoteWithConnectionModelProperty: lambda self, 186 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 187 exp.ReturnsProperty: lambda self, e: ( 188 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 189 ), 190 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 191 exp.SecureProperty: lambda *_: "SECURE", 192 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 193 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 194 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 195 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 196 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 197 exp.SqlReadWriteProperty: lambda _, e: e.name, 198 exp.SqlSecurityProperty: lambda _, 199 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 200 exp.StabilityProperty: lambda _, e: e.name, 201 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 202 exp.StreamingTableProperty: lambda *_: "STREAMING", 203 exp.StrictProperty: lambda *_: "STRICT", 204 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 205 exp.TableColumn: lambda self, e: self.sql(e.this), 206 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 207 exp.TemporaryProperty: lambda *_: "TEMPORARY", 208 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 209 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 210 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 211 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 212 exp.TransientProperty: lambda *_: "TRANSIENT", 213 exp.Union: lambda self, e: self.set_operations(e), 214 exp.UnloggedProperty: lambda *_: "UNLOGGED", 215 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 216 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 217 exp.Uuid: lambda *_: "UUID()", 218 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 219 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 220 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 221 exp.VolatileProperty: lambda *_: "VOLATILE", 222 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 223 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 224 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 225 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 226 exp.ForceProperty: lambda *_: "FORCE", 227 } 228 229 # Whether null ordering is supported in order by 230 # True: Full Support, None: No support, False: No support for certain cases 231 # such as window specifications, aggregate functions etc 232 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 233 234 # Whether ignore nulls is inside the agg or outside. 235 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 236 IGNORE_NULLS_IN_FUNC = False 237 238 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 239 LOCKING_READS_SUPPORTED = False 240 241 # Whether the EXCEPT and INTERSECT operations can return duplicates 242 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 243 244 # Wrap derived values in parens, usually standard but spark doesn't support it 245 WRAP_DERIVED_VALUES = True 246 247 # Whether create function uses an AS before the RETURN 248 CREATE_FUNCTION_RETURN_AS = True 249 250 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 251 MATCHED_BY_SOURCE = True 252 253 # Whether the INTERVAL expression works only with values like '1 day' 254 SINGLE_STRING_INTERVAL = False 255 256 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 257 INTERVAL_ALLOWS_PLURAL_FORM = True 258 259 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 260 LIMIT_FETCH = "ALL" 261 262 # Whether limit and fetch allows expresions or just limits 263 LIMIT_ONLY_LITERALS = False 264 265 # Whether a table is allowed to be renamed with a db 266 RENAME_TABLE_WITH_DB = True 267 268 # The separator for grouping sets and rollups 269 GROUPINGS_SEP = "," 270 271 # The string used for creating an index on a table 272 INDEX_ON = "ON" 273 274 # Whether join hints should be generated 275 JOIN_HINTS = True 276 277 # Whether table hints should be generated 278 TABLE_HINTS = True 279 280 # Whether query hints should be generated 281 QUERY_HINTS = True 282 283 # What kind of separator to use for query hints 284 QUERY_HINT_SEP = ", " 285 286 # Whether comparing against booleans (e.g. x IS TRUE) is supported 287 IS_BOOL_ALLOWED = True 288 289 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 290 DUPLICATE_KEY_UPDATE_WITH_SET = True 291 292 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 293 LIMIT_IS_TOP = False 294 295 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 296 RETURNING_END = True 297 298 # Whether to generate an unquoted value for EXTRACT's date part argument 299 EXTRACT_ALLOWS_QUOTES = True 300 301 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 302 TZ_TO_WITH_TIME_ZONE = False 303 304 # Whether the NVL2 function is supported 305 NVL2_SUPPORTED = True 306 307 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 308 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 309 310 # Whether VALUES statements can be used as derived tables. 311 # MySQL 5 and Redshift do not allow this, so when False, it will convert 312 # SELECT * VALUES into SELECT UNION 313 VALUES_AS_TABLE = True 314 315 # Whether the word COLUMN is included when adding a column with ALTER TABLE 316 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 317 318 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 319 UNNEST_WITH_ORDINALITY = True 320 321 # Whether FILTER (WHERE cond) can be used for conditional aggregation 322 AGGREGATE_FILTER_SUPPORTED = True 323 324 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 325 SEMI_ANTI_JOIN_WITH_SIDE = True 326 327 # Whether to include the type of a computed column in the CREATE DDL 328 COMPUTED_COLUMN_WITH_TYPE = True 329 330 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 331 SUPPORTS_TABLE_COPY = True 332 333 # Whether parentheses are required around the table sample's expression 334 TABLESAMPLE_REQUIRES_PARENS = True 335 336 # Whether a table sample clause's size needs to be followed by the ROWS keyword 337 TABLESAMPLE_SIZE_IS_ROWS = True 338 339 # The keyword(s) to use when generating a sample clause 340 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 341 342 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 343 TABLESAMPLE_WITH_METHOD = True 344 345 # The keyword to use when specifying the seed of a sample clause 346 TABLESAMPLE_SEED_KEYWORD = "SEED" 347 348 # Whether COLLATE is a function instead of a binary operator 349 COLLATE_IS_FUNC = False 350 351 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 352 DATA_TYPE_SPECIFIERS_ALLOWED = False 353 354 # Whether conditions require booleans WHERE x = 0 vs WHERE x 355 ENSURE_BOOLS = False 356 357 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 358 CTE_RECURSIVE_KEYWORD_REQUIRED = True 359 360 # Whether CONCAT requires >1 arguments 361 SUPPORTS_SINGLE_ARG_CONCAT = True 362 363 # Whether LAST_DAY function supports a date part argument 364 LAST_DAY_SUPPORTS_DATE_PART = True 365 366 # Whether named columns are allowed in table aliases 367 SUPPORTS_TABLE_ALIAS_COLUMNS = True 368 369 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 370 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 371 372 # What delimiter to use for separating JSON key/value pairs 373 JSON_KEY_VALUE_PAIR_SEP = ":" 374 375 # INSERT OVERWRITE TABLE x override 376 INSERT_OVERWRITE = " OVERWRITE TABLE" 377 378 # Whether the SELECT .. INTO syntax is used instead of CTAS 379 SUPPORTS_SELECT_INTO = False 380 381 # Whether UNLOGGED tables can be created 382 SUPPORTS_UNLOGGED_TABLES = False 383 384 # Whether the CREATE TABLE LIKE statement is supported 385 SUPPORTS_CREATE_TABLE_LIKE = True 386 387 # Whether the LikeProperty needs to be specified inside of the schema clause 388 LIKE_PROPERTY_INSIDE_SCHEMA = False 389 390 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 391 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 392 MULTI_ARG_DISTINCT = True 393 394 # Whether the JSON extraction operators expect a value of type JSON 395 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 396 397 # Whether bracketed keys like ["foo"] are supported in JSON paths 398 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 399 400 # Whether to escape keys using single quotes in JSON paths 401 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 402 403 # The JSONPathPart expressions supported by this dialect 404 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 405 406 # Whether any(f(x) for x in array) can be implemented by this dialect 407 CAN_IMPLEMENT_ARRAY_ANY = False 408 409 # Whether the function TO_NUMBER is supported 410 SUPPORTS_TO_NUMBER = True 411 412 # Whether EXCLUDE in window specification is supported 413 SUPPORTS_WINDOW_EXCLUDE = False 414 415 # Whether or not set op modifiers apply to the outer set op or select. 416 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 417 # True means limit 1 happens after the set op, False means it it happens on y. 418 SET_OP_MODIFIERS = True 419 420 # Whether parameters from COPY statement are wrapped in parentheses 421 COPY_PARAMS_ARE_WRAPPED = True 422 423 # Whether values of params are set with "=" token or empty space 424 COPY_PARAMS_EQ_REQUIRED = False 425 426 # Whether COPY statement has INTO keyword 427 COPY_HAS_INTO_KEYWORD = True 428 429 # Whether the conditional TRY(expression) function is supported 430 TRY_SUPPORTED = True 431 432 # Whether the UESCAPE syntax in unicode strings is supported 433 SUPPORTS_UESCAPE = True 434 435 # The keyword to use when generating a star projection with excluded columns 436 STAR_EXCEPT = "EXCEPT" 437 438 # The HEX function name 439 HEX_FUNC = "HEX" 440 441 # The keywords to use when prefixing & separating WITH based properties 442 WITH_PROPERTIES_PREFIX = "WITH" 443 444 # Whether to quote the generated expression of exp.JsonPath 445 QUOTE_JSON_PATH = True 446 447 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 448 PAD_FILL_PATTERN_IS_REQUIRED = False 449 450 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 451 SUPPORTS_EXPLODING_PROJECTIONS = True 452 453 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 454 ARRAY_CONCAT_IS_VAR_LEN = True 455 456 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 457 SUPPORTS_CONVERT_TIMEZONE = False 458 459 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 460 SUPPORTS_MEDIAN = True 461 462 # Whether UNIX_SECONDS(timestamp) is supported 463 SUPPORTS_UNIX_SECONDS = False 464 465 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 466 ALTER_SET_WRAPPED = False 467 468 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 469 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 470 # TODO: The normalization should be done by default once we've tested it across all dialects. 471 NORMALIZE_EXTRACT_DATE_PARTS = False 472 473 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 474 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 475 476 # The function name of the exp.ArraySize expression 477 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 478 479 # The syntax to use when altering the type of a column 480 ALTER_SET_TYPE = "SET DATA TYPE" 481 482 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 483 # None -> Doesn't support it at all 484 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 485 # True (Postgres) -> Explicitly requires it 486 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 487 488 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 489 SUPPORTS_DECODE_CASE = True 490 491 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 492 SUPPORTS_BETWEEN_FLAGS = False 493 494 TYPE_MAPPING = { 495 exp.DataType.Type.DATETIME2: "TIMESTAMP", 496 exp.DataType.Type.NCHAR: "CHAR", 497 exp.DataType.Type.NVARCHAR: "VARCHAR", 498 exp.DataType.Type.MEDIUMTEXT: "TEXT", 499 exp.DataType.Type.LONGTEXT: "TEXT", 500 exp.DataType.Type.TINYTEXT: "TEXT", 501 exp.DataType.Type.BLOB: "VARBINARY", 502 exp.DataType.Type.MEDIUMBLOB: "BLOB", 503 exp.DataType.Type.LONGBLOB: "BLOB", 504 exp.DataType.Type.TINYBLOB: "BLOB", 505 exp.DataType.Type.INET: "INET", 506 exp.DataType.Type.ROWVERSION: "VARBINARY", 507 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 508 } 509 510 TIME_PART_SINGULARS = { 511 "MICROSECONDS": "MICROSECOND", 512 "SECONDS": "SECOND", 513 "MINUTES": "MINUTE", 514 "HOURS": "HOUR", 515 "DAYS": "DAY", 516 "WEEKS": "WEEK", 517 "MONTHS": "MONTH", 518 "QUARTERS": "QUARTER", 519 "YEARS": "YEAR", 520 } 521 522 AFTER_HAVING_MODIFIER_TRANSFORMS = { 523 "cluster": lambda self, e: self.sql(e, "cluster"), 524 "distribute": lambda self, e: self.sql(e, "distribute"), 525 "sort": lambda self, e: self.sql(e, "sort"), 526 "windows": lambda self, e: ( 527 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 528 if e.args.get("windows") 529 else "" 530 ), 531 "qualify": lambda self, e: self.sql(e, "qualify"), 532 } 533 534 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 535 536 STRUCT_DELIMITER = ("<", ">") 537 538 PARAMETER_TOKEN = "@" 539 NAMED_PLACEHOLDER_TOKEN = ":" 540 541 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 542 543 PROPERTIES_LOCATION = { 544 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 546 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 547 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 550 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 552 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 555 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 559 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 560 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 561 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 562 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 564 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 568 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 572 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 573 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 574 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 575 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 576 exp.HeapProperty: exp.Properties.Location.POST_WITH, 577 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 579 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 582 exp.JournalProperty: exp.Properties.Location.POST_NAME, 583 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 584 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 588 exp.LogProperty: exp.Properties.Location.POST_NAME, 589 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 590 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 591 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 592 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 594 exp.Order: exp.Properties.Location.POST_SCHEMA, 595 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 597 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 599 exp.Property: exp.Properties.Location.POST_WITH, 600 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 608 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 609 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 610 exp.Set: exp.Properties.Location.POST_SCHEMA, 611 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.SetProperty: exp.Properties.Location.POST_CREATE, 613 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 614 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 615 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 616 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 619 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 620 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 622 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.Tags: exp.Properties.Location.POST_WITH, 624 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 625 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 626 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 627 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 628 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 629 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 630 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 631 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 633 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 634 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 635 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 636 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 637 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 639 } 640 641 # Keywords that can't be used as unquoted identifier names 642 RESERVED_KEYWORDS: t.Set[str] = set() 643 644 # Expressions whose comments are separated from them for better formatting 645 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 646 exp.Command, 647 exp.Create, 648 exp.Describe, 649 exp.Delete, 650 exp.Drop, 651 exp.From, 652 exp.Insert, 653 exp.Join, 654 exp.MultitableInserts, 655 exp.Order, 656 exp.Group, 657 exp.Having, 658 exp.Select, 659 exp.SetOperation, 660 exp.Update, 661 exp.Where, 662 exp.With, 663 ) 664 665 # Expressions that should not have their comments generated in maybe_comment 666 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 667 exp.Binary, 668 exp.SetOperation, 669 ) 670 671 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 672 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 673 exp.Column, 674 exp.Literal, 675 exp.Neg, 676 exp.Paren, 677 ) 678 679 PARAMETERIZABLE_TEXT_TYPES = { 680 exp.DataType.Type.NVARCHAR, 681 exp.DataType.Type.VARCHAR, 682 exp.DataType.Type.CHAR, 683 exp.DataType.Type.NCHAR, 684 } 685 686 # Expressions that need to have all CTEs under them bubbled up to them 687 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 688 689 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 690 691 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 692 693 __slots__ = ( 694 "pretty", 695 "identify", 696 "normalize", 697 "pad", 698 "_indent", 699 "normalize_functions", 700 "unsupported_level", 701 "max_unsupported", 702 "leading_comma", 703 "max_text_width", 704 "comments", 705 "dialect", 706 "unsupported_messages", 707 "_escaped_quote_end", 708 "_escaped_identifier_end", 709 "_next_name", 710 "_identifier_start", 711 "_identifier_end", 712 "_quote_json_path_key_using_brackets", 713 ) 714 715 def __init__( 716 self, 717 pretty: t.Optional[bool] = None, 718 identify: str | bool = False, 719 normalize: bool = False, 720 pad: int = 2, 721 indent: int = 2, 722 normalize_functions: t.Optional[str | bool] = None, 723 unsupported_level: ErrorLevel = ErrorLevel.WARN, 724 max_unsupported: int = 3, 725 leading_comma: bool = False, 726 max_text_width: int = 80, 727 comments: bool = True, 728 dialect: DialectType = None, 729 ): 730 import sqlglot 731 from sqlglot.dialects import Dialect 732 733 self.pretty = pretty if pretty is not None else sqlglot.pretty 734 self.identify = identify 735 self.normalize = normalize 736 self.pad = pad 737 self._indent = indent 738 self.unsupported_level = unsupported_level 739 self.max_unsupported = max_unsupported 740 self.leading_comma = leading_comma 741 self.max_text_width = max_text_width 742 self.comments = comments 743 self.dialect = Dialect.get_or_raise(dialect) 744 745 # This is both a Dialect property and a Generator argument, so we prioritize the latter 746 self.normalize_functions = ( 747 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 748 ) 749 750 self.unsupported_messages: t.List[str] = [] 751 self._escaped_quote_end: str = ( 752 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 753 ) 754 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 755 756 self._next_name = name_sequence("_t") 757 758 self._identifier_start = self.dialect.IDENTIFIER_START 759 self._identifier_end = self.dialect.IDENTIFIER_END 760 761 self._quote_json_path_key_using_brackets = True 762 763 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 764 """ 765 Generates the SQL string corresponding to the given syntax tree. 766 767 Args: 768 expression: The syntax tree. 769 copy: Whether to copy the expression. The generator performs mutations so 770 it is safer to copy. 771 772 Returns: 773 The SQL string corresponding to `expression`. 774 """ 775 if copy: 776 expression = expression.copy() 777 778 expression = self.preprocess(expression) 779 780 self.unsupported_messages = [] 781 sql = self.sql(expression).strip() 782 783 if self.pretty: 784 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 785 786 if self.unsupported_level == ErrorLevel.IGNORE: 787 return sql 788 789 if self.unsupported_level == ErrorLevel.WARN: 790 for msg in self.unsupported_messages: 791 logger.warning(msg) 792 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 793 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 794 795 return sql 796 797 def preprocess(self, expression: exp.Expression) -> exp.Expression: 798 """Apply generic preprocessing transformations to a given expression.""" 799 expression = self._move_ctes_to_top_level(expression) 800 801 if self.ENSURE_BOOLS: 802 from sqlglot.transforms import ensure_bools 803 804 expression = ensure_bools(expression) 805 806 return expression 807 808 def _move_ctes_to_top_level(self, expression: E) -> E: 809 if ( 810 not expression.parent 811 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 812 and any(node.parent is not expression for node in expression.find_all(exp.With)) 813 ): 814 from sqlglot.transforms import move_ctes_to_top_level 815 816 expression = move_ctes_to_top_level(expression) 817 return expression 818 819 def unsupported(self, message: str) -> None: 820 if self.unsupported_level == ErrorLevel.IMMEDIATE: 821 raise UnsupportedError(message) 822 self.unsupported_messages.append(message) 823 824 def sep(self, sep: str = " ") -> str: 825 return f"{sep.strip()}\n" if self.pretty else sep 826 827 def seg(self, sql: str, sep: str = " ") -> str: 828 return f"{self.sep(sep)}{sql}" 829 830 def sanitize_comment(self, comment: str) -> str: 831 comment = " " + comment if comment[0].strip() else comment 832 comment = comment + " " if comment[-1].strip() else comment 833 834 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 835 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 836 comment = comment.replace("*/", "* /") 837 838 return comment 839 840 def maybe_comment( 841 self, 842 sql: str, 843 expression: t.Optional[exp.Expression] = None, 844 comments: t.Optional[t.List[str]] = None, 845 separated: bool = False, 846 ) -> str: 847 comments = ( 848 ((expression and expression.comments) if comments is None else comments) # type: ignore 849 if self.comments 850 else None 851 ) 852 853 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 854 return sql 855 856 comments_sql = " ".join( 857 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 858 ) 859 860 if not comments_sql: 861 return sql 862 863 comments_sql = self._replace_line_breaks(comments_sql) 864 865 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 866 return ( 867 f"{self.sep()}{comments_sql}{sql}" 868 if not sql or sql[0].isspace() 869 else f"{comments_sql}{self.sep()}{sql}" 870 ) 871 872 return f"{sql} {comments_sql}" 873 874 def wrap(self, expression: exp.Expression | str) -> str: 875 this_sql = ( 876 self.sql(expression) 877 if isinstance(expression, exp.UNWRAPPED_QUERIES) 878 else self.sql(expression, "this") 879 ) 880 if not this_sql: 881 return "()" 882 883 this_sql = self.indent(this_sql, level=1, pad=0) 884 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 885 886 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 887 original = self.identify 888 self.identify = False 889 result = func(*args, **kwargs) 890 self.identify = original 891 return result 892 893 def normalize_func(self, name: str) -> str: 894 if self.normalize_functions == "upper" or self.normalize_functions is True: 895 return name.upper() 896 if self.normalize_functions == "lower": 897 return name.lower() 898 return name 899 900 def indent( 901 self, 902 sql: str, 903 level: int = 0, 904 pad: t.Optional[int] = None, 905 skip_first: bool = False, 906 skip_last: bool = False, 907 ) -> str: 908 if not self.pretty or not sql: 909 return sql 910 911 pad = self.pad if pad is None else pad 912 lines = sql.split("\n") 913 914 return "\n".join( 915 ( 916 line 917 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 918 else f"{' ' * (level * self._indent + pad)}{line}" 919 ) 920 for i, line in enumerate(lines) 921 ) 922 923 def sql( 924 self, 925 expression: t.Optional[str | exp.Expression], 926 key: t.Optional[str] = None, 927 comment: bool = True, 928 ) -> str: 929 if not expression: 930 return "" 931 932 if isinstance(expression, str): 933 return expression 934 935 if key: 936 value = expression.args.get(key) 937 if value: 938 return self.sql(value) 939 return "" 940 941 transform = self.TRANSFORMS.get(expression.__class__) 942 943 if callable(transform): 944 sql = transform(self, expression) 945 elif isinstance(expression, exp.Expression): 946 exp_handler_name = f"{expression.key}_sql" 947 948 if hasattr(self, exp_handler_name): 949 sql = getattr(self, exp_handler_name)(expression) 950 elif isinstance(expression, exp.Func): 951 sql = self.function_fallback_sql(expression) 952 elif isinstance(expression, exp.Property): 953 sql = self.property_sql(expression) 954 else: 955 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 956 else: 957 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 958 959 return self.maybe_comment(sql, expression) if self.comments and comment else sql 960 961 def uncache_sql(self, expression: exp.Uncache) -> str: 962 table = self.sql(expression, "this") 963 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 964 return f"UNCACHE TABLE{exists_sql} {table}" 965 966 def cache_sql(self, expression: exp.Cache) -> str: 967 lazy = " LAZY" if expression.args.get("lazy") else "" 968 table = self.sql(expression, "this") 969 options = expression.args.get("options") 970 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 971 sql = self.sql(expression, "expression") 972 sql = f" AS{self.sep()}{sql}" if sql else "" 973 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 974 return self.prepend_ctes(expression, sql) 975 976 def characterset_sql(self, expression: exp.CharacterSet) -> str: 977 if isinstance(expression.parent, exp.Cast): 978 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 979 default = "DEFAULT " if expression.args.get("default") else "" 980 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 981 982 def column_parts(self, expression: exp.Column) -> str: 983 return ".".join( 984 self.sql(part) 985 for part in ( 986 expression.args.get("catalog"), 987 expression.args.get("db"), 988 expression.args.get("table"), 989 expression.args.get("this"), 990 ) 991 if part 992 ) 993 994 def column_sql(self, expression: exp.Column) -> str: 995 join_mark = " (+)" if expression.args.get("join_mark") else "" 996 997 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 998 join_mark = "" 999 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1000 1001 return f"{self.column_parts(expression)}{join_mark}" 1002 1003 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1004 this = self.sql(expression, "this") 1005 this = f" {this}" if this else "" 1006 position = self.sql(expression, "position") 1007 return f"{position}{this}" 1008 1009 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1010 column = self.sql(expression, "this") 1011 kind = self.sql(expression, "kind") 1012 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1013 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1014 kind = f"{sep}{kind}" if kind else "" 1015 constraints = f" {constraints}" if constraints else "" 1016 position = self.sql(expression, "position") 1017 position = f" {position}" if position else "" 1018 1019 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1020 kind = "" 1021 1022 return f"{exists}{column}{kind}{constraints}{position}" 1023 1024 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1025 this = self.sql(expression, "this") 1026 kind_sql = self.sql(expression, "kind").strip() 1027 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1028 1029 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1030 this = self.sql(expression, "this") 1031 if expression.args.get("not_null"): 1032 persisted = " PERSISTED NOT NULL" 1033 elif expression.args.get("persisted"): 1034 persisted = " PERSISTED" 1035 else: 1036 persisted = "" 1037 1038 return f"AS {this}{persisted}" 1039 1040 def autoincrementcolumnconstraint_sql(self, _) -> str: 1041 return self.token_sql(TokenType.AUTO_INCREMENT) 1042 1043 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1044 if isinstance(expression.this, list): 1045 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1046 else: 1047 this = self.sql(expression, "this") 1048 1049 return f"COMPRESS {this}" 1050 1051 def generatedasidentitycolumnconstraint_sql( 1052 self, expression: exp.GeneratedAsIdentityColumnConstraint 1053 ) -> str: 1054 this = "" 1055 if expression.this is not None: 1056 on_null = " ON NULL" if expression.args.get("on_null") else "" 1057 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1058 1059 start = expression.args.get("start") 1060 start = f"START WITH {start}" if start else "" 1061 increment = expression.args.get("increment") 1062 increment = f" INCREMENT BY {increment}" if increment else "" 1063 minvalue = expression.args.get("minvalue") 1064 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1065 maxvalue = expression.args.get("maxvalue") 1066 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1067 cycle = expression.args.get("cycle") 1068 cycle_sql = "" 1069 1070 if cycle is not None: 1071 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1072 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1073 1074 sequence_opts = "" 1075 if start or increment or cycle_sql: 1076 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1077 sequence_opts = f" ({sequence_opts.strip()})" 1078 1079 expr = self.sql(expression, "expression") 1080 expr = f"({expr})" if expr else "IDENTITY" 1081 1082 return f"GENERATED{this} AS {expr}{sequence_opts}" 1083 1084 def generatedasrowcolumnconstraint_sql( 1085 self, expression: exp.GeneratedAsRowColumnConstraint 1086 ) -> str: 1087 start = "START" if expression.args.get("start") else "END" 1088 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1089 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1090 1091 def periodforsystemtimeconstraint_sql( 1092 self, expression: exp.PeriodForSystemTimeConstraint 1093 ) -> str: 1094 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1095 1096 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1097 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1098 1099 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1100 desc = expression.args.get("desc") 1101 if desc is not None: 1102 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1103 options = self.expressions(expression, key="options", flat=True, sep=" ") 1104 options = f" {options}" if options else "" 1105 return f"PRIMARY KEY{options}" 1106 1107 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1108 this = self.sql(expression, "this") 1109 this = f" {this}" if this else "" 1110 index_type = expression.args.get("index_type") 1111 index_type = f" USING {index_type}" if index_type else "" 1112 on_conflict = self.sql(expression, "on_conflict") 1113 on_conflict = f" {on_conflict}" if on_conflict else "" 1114 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1115 options = self.expressions(expression, key="options", flat=True, sep=" ") 1116 options = f" {options}" if options else "" 1117 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1118 1119 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1120 return self.sql(expression, "this") 1121 1122 def create_sql(self, expression: exp.Create) -> str: 1123 kind = self.sql(expression, "kind") 1124 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1125 properties = expression.args.get("properties") 1126 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1127 1128 this = self.createable_sql(expression, properties_locs) 1129 1130 properties_sql = "" 1131 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1132 exp.Properties.Location.POST_WITH 1133 ): 1134 properties_sql = self.sql( 1135 exp.Properties( 1136 expressions=[ 1137 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1138 *properties_locs[exp.Properties.Location.POST_WITH], 1139 ] 1140 ) 1141 ) 1142 1143 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1144 properties_sql = self.sep() + properties_sql 1145 elif not self.pretty: 1146 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1147 properties_sql = f" {properties_sql}" 1148 1149 begin = " BEGIN" if expression.args.get("begin") else "" 1150 end = " END" if expression.args.get("end") else "" 1151 1152 expression_sql = self.sql(expression, "expression") 1153 if expression_sql: 1154 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1155 1156 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1157 postalias_props_sql = "" 1158 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1159 postalias_props_sql = self.properties( 1160 exp.Properties( 1161 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1162 ), 1163 wrapped=False, 1164 ) 1165 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1166 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1167 1168 postindex_props_sql = "" 1169 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1170 postindex_props_sql = self.properties( 1171 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1172 wrapped=False, 1173 prefix=" ", 1174 ) 1175 1176 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1177 indexes = f" {indexes}" if indexes else "" 1178 index_sql = indexes + postindex_props_sql 1179 1180 replace = " OR REPLACE" if expression.args.get("replace") else "" 1181 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1182 unique = " UNIQUE" if expression.args.get("unique") else "" 1183 1184 clustered = expression.args.get("clustered") 1185 if clustered is None: 1186 clustered_sql = "" 1187 elif clustered: 1188 clustered_sql = " CLUSTERED COLUMNSTORE" 1189 else: 1190 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1191 1192 postcreate_props_sql = "" 1193 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1194 postcreate_props_sql = self.properties( 1195 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1196 sep=" ", 1197 prefix=" ", 1198 wrapped=False, 1199 ) 1200 1201 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1202 1203 postexpression_props_sql = "" 1204 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1205 postexpression_props_sql = self.properties( 1206 exp.Properties( 1207 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1208 ), 1209 sep=" ", 1210 prefix=" ", 1211 wrapped=False, 1212 ) 1213 1214 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1215 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1216 no_schema_binding = ( 1217 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1218 ) 1219 1220 clone = self.sql(expression, "clone") 1221 clone = f" {clone}" if clone else "" 1222 1223 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1224 properties_expression = f"{expression_sql}{properties_sql}" 1225 else: 1226 properties_expression = f"{properties_sql}{expression_sql}" 1227 1228 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1229 return self.prepend_ctes(expression, expression_sql) 1230 1231 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1232 start = self.sql(expression, "start") 1233 start = f"START WITH {start}" if start else "" 1234 increment = self.sql(expression, "increment") 1235 increment = f" INCREMENT BY {increment}" if increment else "" 1236 minvalue = self.sql(expression, "minvalue") 1237 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1238 maxvalue = self.sql(expression, "maxvalue") 1239 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1240 owned = self.sql(expression, "owned") 1241 owned = f" OWNED BY {owned}" if owned else "" 1242 1243 cache = expression.args.get("cache") 1244 if cache is None: 1245 cache_str = "" 1246 elif cache is True: 1247 cache_str = " CACHE" 1248 else: 1249 cache_str = f" CACHE {cache}" 1250 1251 options = self.expressions(expression, key="options", flat=True, sep=" ") 1252 options = f" {options}" if options else "" 1253 1254 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1255 1256 def clone_sql(self, expression: exp.Clone) -> str: 1257 this = self.sql(expression, "this") 1258 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1259 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1260 return f"{shallow}{keyword} {this}" 1261 1262 def describe_sql(self, expression: exp.Describe) -> str: 1263 style = expression.args.get("style") 1264 style = f" {style}" if style else "" 1265 partition = self.sql(expression, "partition") 1266 partition = f" {partition}" if partition else "" 1267 format = self.sql(expression, "format") 1268 format = f" {format}" if format else "" 1269 1270 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1271 1272 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1273 tag = self.sql(expression, "tag") 1274 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1275 1276 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1277 with_ = self.sql(expression, "with") 1278 if with_: 1279 sql = f"{with_}{self.sep()}{sql}" 1280 return sql 1281 1282 def with_sql(self, expression: exp.With) -> str: 1283 sql = self.expressions(expression, flat=True) 1284 recursive = ( 1285 "RECURSIVE " 1286 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1287 else "" 1288 ) 1289 search = self.sql(expression, "search") 1290 search = f" {search}" if search else "" 1291 1292 return f"WITH {recursive}{sql}{search}" 1293 1294 def cte_sql(self, expression: exp.CTE) -> str: 1295 alias = expression.args.get("alias") 1296 if alias: 1297 alias.add_comments(expression.pop_comments()) 1298 1299 alias_sql = self.sql(expression, "alias") 1300 1301 materialized = expression.args.get("materialized") 1302 if materialized is False: 1303 materialized = "NOT MATERIALIZED " 1304 elif materialized: 1305 materialized = "MATERIALIZED " 1306 1307 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1308 1309 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1310 alias = self.sql(expression, "this") 1311 columns = self.expressions(expression, key="columns", flat=True) 1312 columns = f"({columns})" if columns else "" 1313 1314 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1315 columns = "" 1316 self.unsupported("Named columns are not supported in table alias.") 1317 1318 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1319 alias = self._next_name() 1320 1321 return f"{alias}{columns}" 1322 1323 def bitstring_sql(self, expression: exp.BitString) -> str: 1324 this = self.sql(expression, "this") 1325 if self.dialect.BIT_START: 1326 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1327 return f"{int(this, 2)}" 1328 1329 def hexstring_sql( 1330 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1331 ) -> str: 1332 this = self.sql(expression, "this") 1333 is_integer_type = expression.args.get("is_integer") 1334 1335 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1336 not self.dialect.HEX_START and not binary_function_repr 1337 ): 1338 # Integer representation will be returned if: 1339 # - The read dialect treats the hex value as integer literal but not the write 1340 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1341 return f"{int(this, 16)}" 1342 1343 if not is_integer_type: 1344 # Read dialect treats the hex value as BINARY/BLOB 1345 if binary_function_repr: 1346 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1347 return self.func(binary_function_repr, exp.Literal.string(this)) 1348 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1349 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1350 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1351 1352 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1353 1354 def bytestring_sql(self, expression: exp.ByteString) -> str: 1355 this = self.sql(expression, "this") 1356 if self.dialect.BYTE_START: 1357 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1358 return this 1359 1360 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1361 this = self.sql(expression, "this") 1362 escape = expression.args.get("escape") 1363 1364 if self.dialect.UNICODE_START: 1365 escape_substitute = r"\\\1" 1366 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1367 else: 1368 escape_substitute = r"\\u\1" 1369 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1370 1371 if escape: 1372 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1373 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1374 else: 1375 escape_pattern = ESCAPED_UNICODE_RE 1376 escape_sql = "" 1377 1378 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1379 this = escape_pattern.sub(escape_substitute, this) 1380 1381 return f"{left_quote}{this}{right_quote}{escape_sql}" 1382 1383 def rawstring_sql(self, expression: exp.RawString) -> str: 1384 string = expression.this 1385 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1386 string = string.replace("\\", "\\\\") 1387 1388 string = self.escape_str(string, escape_backslash=False) 1389 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1390 1391 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1392 this = self.sql(expression, "this") 1393 specifier = self.sql(expression, "expression") 1394 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1395 return f"{this}{specifier}" 1396 1397 def datatype_sql(self, expression: exp.DataType) -> str: 1398 nested = "" 1399 values = "" 1400 interior = self.expressions(expression, flat=True) 1401 1402 type_value = expression.this 1403 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1404 type_sql = self.sql(expression, "kind") 1405 else: 1406 type_sql = ( 1407 self.TYPE_MAPPING.get(type_value, type_value.value) 1408 if isinstance(type_value, exp.DataType.Type) 1409 else type_value 1410 ) 1411 1412 if interior: 1413 if expression.args.get("nested"): 1414 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1415 if expression.args.get("values") is not None: 1416 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1417 values = self.expressions(expression, key="values", flat=True) 1418 values = f"{delimiters[0]}{values}{delimiters[1]}" 1419 elif type_value == exp.DataType.Type.INTERVAL: 1420 nested = f" {interior}" 1421 else: 1422 nested = f"({interior})" 1423 1424 type_sql = f"{type_sql}{nested}{values}" 1425 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1426 exp.DataType.Type.TIMETZ, 1427 exp.DataType.Type.TIMESTAMPTZ, 1428 ): 1429 type_sql = f"{type_sql} WITH TIME ZONE" 1430 1431 return type_sql 1432 1433 def directory_sql(self, expression: exp.Directory) -> str: 1434 local = "LOCAL " if expression.args.get("local") else "" 1435 row_format = self.sql(expression, "row_format") 1436 row_format = f" {row_format}" if row_format else "" 1437 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1438 1439 def delete_sql(self, expression: exp.Delete) -> str: 1440 this = self.sql(expression, "this") 1441 this = f" FROM {this}" if this else "" 1442 using = self.sql(expression, "using") 1443 using = f" USING {using}" if using else "" 1444 cluster = self.sql(expression, "cluster") 1445 cluster = f" {cluster}" if cluster else "" 1446 where = self.sql(expression, "where") 1447 returning = self.sql(expression, "returning") 1448 limit = self.sql(expression, "limit") 1449 tables = self.expressions(expression, key="tables") 1450 tables = f" {tables}" if tables else "" 1451 if self.RETURNING_END: 1452 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1453 else: 1454 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1455 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1456 1457 def drop_sql(self, expression: exp.Drop) -> str: 1458 this = self.sql(expression, "this") 1459 expressions = self.expressions(expression, flat=True) 1460 expressions = f" ({expressions})" if expressions else "" 1461 kind = expression.args["kind"] 1462 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1463 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1464 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1465 on_cluster = self.sql(expression, "cluster") 1466 on_cluster = f" {on_cluster}" if on_cluster else "" 1467 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1468 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1469 cascade = " CASCADE" if expression.args.get("cascade") else "" 1470 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1471 purge = " PURGE" if expression.args.get("purge") else "" 1472 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1473 1474 def set_operation(self, expression: exp.SetOperation) -> str: 1475 op_type = type(expression) 1476 op_name = op_type.key.upper() 1477 1478 distinct = expression.args.get("distinct") 1479 if ( 1480 distinct is False 1481 and op_type in (exp.Except, exp.Intersect) 1482 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1483 ): 1484 self.unsupported(f"{op_name} ALL is not supported") 1485 1486 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1487 1488 if distinct is None: 1489 distinct = default_distinct 1490 if distinct is None: 1491 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1492 1493 if distinct is default_distinct: 1494 distinct_or_all = "" 1495 else: 1496 distinct_or_all = " DISTINCT" if distinct else " ALL" 1497 1498 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1499 side_kind = f"{side_kind} " if side_kind else "" 1500 1501 by_name = " BY NAME" if expression.args.get("by_name") else "" 1502 on = self.expressions(expression, key="on", flat=True) 1503 on = f" ON ({on})" if on else "" 1504 1505 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1506 1507 def set_operations(self, expression: exp.SetOperation) -> str: 1508 if not self.SET_OP_MODIFIERS: 1509 limit = expression.args.get("limit") 1510 order = expression.args.get("order") 1511 1512 if limit or order: 1513 select = self._move_ctes_to_top_level( 1514 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1515 ) 1516 1517 if limit: 1518 select = select.limit(limit.pop(), copy=False) 1519 if order: 1520 select = select.order_by(order.pop(), copy=False) 1521 return self.sql(select) 1522 1523 sqls: t.List[str] = [] 1524 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1525 1526 while stack: 1527 node = stack.pop() 1528 1529 if isinstance(node, exp.SetOperation): 1530 stack.append(node.expression) 1531 stack.append( 1532 self.maybe_comment( 1533 self.set_operation(node), comments=node.comments, separated=True 1534 ) 1535 ) 1536 stack.append(node.this) 1537 else: 1538 sqls.append(self.sql(node)) 1539 1540 this = self.sep().join(sqls) 1541 this = self.query_modifiers(expression, this) 1542 return self.prepend_ctes(expression, this) 1543 1544 def fetch_sql(self, expression: exp.Fetch) -> str: 1545 direction = expression.args.get("direction") 1546 direction = f" {direction}" if direction else "" 1547 count = self.sql(expression, "count") 1548 count = f" {count}" if count else "" 1549 limit_options = self.sql(expression, "limit_options") 1550 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1551 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1552 1553 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1554 percent = " PERCENT" if expression.args.get("percent") else "" 1555 rows = " ROWS" if expression.args.get("rows") else "" 1556 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1557 if not with_ties and rows: 1558 with_ties = " ONLY" 1559 return f"{percent}{rows}{with_ties}" 1560 1561 def filter_sql(self, expression: exp.Filter) -> str: 1562 if self.AGGREGATE_FILTER_SUPPORTED: 1563 this = self.sql(expression, "this") 1564 where = self.sql(expression, "expression").strip() 1565 return f"{this} FILTER({where})" 1566 1567 agg = expression.this 1568 agg_arg = agg.this 1569 cond = expression.expression.this 1570 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1571 return self.sql(agg) 1572 1573 def hint_sql(self, expression: exp.Hint) -> str: 1574 if not self.QUERY_HINTS: 1575 self.unsupported("Hints are not supported") 1576 return "" 1577 1578 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1579 1580 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1581 using = self.sql(expression, "using") 1582 using = f" USING {using}" if using else "" 1583 columns = self.expressions(expression, key="columns", flat=True) 1584 columns = f"({columns})" if columns else "" 1585 partition_by = self.expressions(expression, key="partition_by", flat=True) 1586 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1587 where = self.sql(expression, "where") 1588 include = self.expressions(expression, key="include", flat=True) 1589 if include: 1590 include = f" INCLUDE ({include})" 1591 with_storage = self.expressions(expression, key="with_storage", flat=True) 1592 with_storage = f" WITH ({with_storage})" if with_storage else "" 1593 tablespace = self.sql(expression, "tablespace") 1594 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1595 on = self.sql(expression, "on") 1596 on = f" ON {on}" if on else "" 1597 1598 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1599 1600 def index_sql(self, expression: exp.Index) -> str: 1601 unique = "UNIQUE " if expression.args.get("unique") else "" 1602 primary = "PRIMARY " if expression.args.get("primary") else "" 1603 amp = "AMP " if expression.args.get("amp") else "" 1604 name = self.sql(expression, "this") 1605 name = f"{name} " if name else "" 1606 table = self.sql(expression, "table") 1607 table = f"{self.INDEX_ON} {table}" if table else "" 1608 1609 index = "INDEX " if not table else "" 1610 1611 params = self.sql(expression, "params") 1612 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1613 1614 def identifier_sql(self, expression: exp.Identifier) -> str: 1615 text = expression.name 1616 lower = text.lower() 1617 text = lower if self.normalize and not expression.quoted else text 1618 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1619 if ( 1620 expression.quoted 1621 or self.dialect.can_identify(text, self.identify) 1622 or lower in self.RESERVED_KEYWORDS 1623 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1624 ): 1625 text = f"{self._identifier_start}{text}{self._identifier_end}" 1626 return text 1627 1628 def hex_sql(self, expression: exp.Hex) -> str: 1629 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1630 if self.dialect.HEX_LOWERCASE: 1631 text = self.func("LOWER", text) 1632 1633 return text 1634 1635 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1636 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1637 if not self.dialect.HEX_LOWERCASE: 1638 text = self.func("LOWER", text) 1639 return text 1640 1641 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1642 input_format = self.sql(expression, "input_format") 1643 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1644 output_format = self.sql(expression, "output_format") 1645 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1646 return self.sep().join((input_format, output_format)) 1647 1648 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1649 string = self.sql(exp.Literal.string(expression.name)) 1650 return f"{prefix}{string}" 1651 1652 def partition_sql(self, expression: exp.Partition) -> str: 1653 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1654 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1655 1656 def properties_sql(self, expression: exp.Properties) -> str: 1657 root_properties = [] 1658 with_properties = [] 1659 1660 for p in expression.expressions: 1661 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1662 if p_loc == exp.Properties.Location.POST_WITH: 1663 with_properties.append(p) 1664 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1665 root_properties.append(p) 1666 1667 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1668 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1669 1670 if root_props and with_props and not self.pretty: 1671 with_props = " " + with_props 1672 1673 return root_props + with_props 1674 1675 def root_properties(self, properties: exp.Properties) -> str: 1676 if properties.expressions: 1677 return self.expressions(properties, indent=False, sep=" ") 1678 return "" 1679 1680 def properties( 1681 self, 1682 properties: exp.Properties, 1683 prefix: str = "", 1684 sep: str = ", ", 1685 suffix: str = "", 1686 wrapped: bool = True, 1687 ) -> str: 1688 if properties.expressions: 1689 expressions = self.expressions(properties, sep=sep, indent=False) 1690 if expressions: 1691 expressions = self.wrap(expressions) if wrapped else expressions 1692 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1693 return "" 1694 1695 def with_properties(self, properties: exp.Properties) -> str: 1696 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1697 1698 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1699 properties_locs = defaultdict(list) 1700 for p in properties.expressions: 1701 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1702 if p_loc != exp.Properties.Location.UNSUPPORTED: 1703 properties_locs[p_loc].append(p) 1704 else: 1705 self.unsupported(f"Unsupported property {p.key}") 1706 1707 return properties_locs 1708 1709 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1710 if isinstance(expression.this, exp.Dot): 1711 return self.sql(expression, "this") 1712 return f"'{expression.name}'" if string_key else expression.name 1713 1714 def property_sql(self, expression: exp.Property) -> str: 1715 property_cls = expression.__class__ 1716 if property_cls == exp.Property: 1717 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1718 1719 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1720 if not property_name: 1721 self.unsupported(f"Unsupported property {expression.key}") 1722 1723 return f"{property_name}={self.sql(expression, 'this')}" 1724 1725 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1726 if self.SUPPORTS_CREATE_TABLE_LIKE: 1727 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1728 options = f" {options}" if options else "" 1729 1730 like = f"LIKE {self.sql(expression, 'this')}{options}" 1731 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1732 like = f"({like})" 1733 1734 return like 1735 1736 if expression.expressions: 1737 self.unsupported("Transpilation of LIKE property options is unsupported") 1738 1739 select = exp.select("*").from_(expression.this).limit(0) 1740 return f"AS {self.sql(select)}" 1741 1742 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1743 no = "NO " if expression.args.get("no") else "" 1744 protection = " PROTECTION" if expression.args.get("protection") else "" 1745 return f"{no}FALLBACK{protection}" 1746 1747 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1748 no = "NO " if expression.args.get("no") else "" 1749 local = expression.args.get("local") 1750 local = f"{local} " if local else "" 1751 dual = "DUAL " if expression.args.get("dual") else "" 1752 before = "BEFORE " if expression.args.get("before") else "" 1753 after = "AFTER " if expression.args.get("after") else "" 1754 return f"{no}{local}{dual}{before}{after}JOURNAL" 1755 1756 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1757 freespace = self.sql(expression, "this") 1758 percent = " PERCENT" if expression.args.get("percent") else "" 1759 return f"FREESPACE={freespace}{percent}" 1760 1761 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1762 if expression.args.get("default"): 1763 property = "DEFAULT" 1764 elif expression.args.get("on"): 1765 property = "ON" 1766 else: 1767 property = "OFF" 1768 return f"CHECKSUM={property}" 1769 1770 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1771 if expression.args.get("no"): 1772 return "NO MERGEBLOCKRATIO" 1773 if expression.args.get("default"): 1774 return "DEFAULT MERGEBLOCKRATIO" 1775 1776 percent = " PERCENT" if expression.args.get("percent") else "" 1777 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1778 1779 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1780 default = expression.args.get("default") 1781 minimum = expression.args.get("minimum") 1782 maximum = expression.args.get("maximum") 1783 if default or minimum or maximum: 1784 if default: 1785 prop = "DEFAULT" 1786 elif minimum: 1787 prop = "MINIMUM" 1788 else: 1789 prop = "MAXIMUM" 1790 return f"{prop} DATABLOCKSIZE" 1791 units = expression.args.get("units") 1792 units = f" {units}" if units else "" 1793 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1794 1795 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1796 autotemp = expression.args.get("autotemp") 1797 always = expression.args.get("always") 1798 default = expression.args.get("default") 1799 manual = expression.args.get("manual") 1800 never = expression.args.get("never") 1801 1802 if autotemp is not None: 1803 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1804 elif always: 1805 prop = "ALWAYS" 1806 elif default: 1807 prop = "DEFAULT" 1808 elif manual: 1809 prop = "MANUAL" 1810 elif never: 1811 prop = "NEVER" 1812 return f"BLOCKCOMPRESSION={prop}" 1813 1814 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1815 no = expression.args.get("no") 1816 no = " NO" if no else "" 1817 concurrent = expression.args.get("concurrent") 1818 concurrent = " CONCURRENT" if concurrent else "" 1819 target = self.sql(expression, "target") 1820 target = f" {target}" if target else "" 1821 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1822 1823 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1824 if isinstance(expression.this, list): 1825 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1826 if expression.this: 1827 modulus = self.sql(expression, "this") 1828 remainder = self.sql(expression, "expression") 1829 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1830 1831 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1832 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1833 return f"FROM ({from_expressions}) TO ({to_expressions})" 1834 1835 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1836 this = self.sql(expression, "this") 1837 1838 for_values_or_default = expression.expression 1839 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1840 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1841 else: 1842 for_values_or_default = " DEFAULT" 1843 1844 return f"PARTITION OF {this}{for_values_or_default}" 1845 1846 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1847 kind = expression.args.get("kind") 1848 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1849 for_or_in = expression.args.get("for_or_in") 1850 for_or_in = f" {for_or_in}" if for_or_in else "" 1851 lock_type = expression.args.get("lock_type") 1852 override = " OVERRIDE" if expression.args.get("override") else "" 1853 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1854 1855 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1856 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1857 statistics = expression.args.get("statistics") 1858 statistics_sql = "" 1859 if statistics is not None: 1860 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1861 return f"{data_sql}{statistics_sql}" 1862 1863 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1864 this = self.sql(expression, "this") 1865 this = f"HISTORY_TABLE={this}" if this else "" 1866 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1867 data_consistency = ( 1868 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1869 ) 1870 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1871 retention_period = ( 1872 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1873 ) 1874 1875 if this: 1876 on_sql = self.func("ON", this, data_consistency, retention_period) 1877 else: 1878 on_sql = "ON" if expression.args.get("on") else "OFF" 1879 1880 sql = f"SYSTEM_VERSIONING={on_sql}" 1881 1882 return f"WITH({sql})" if expression.args.get("with") else sql 1883 1884 def insert_sql(self, expression: exp.Insert) -> str: 1885 hint = self.sql(expression, "hint") 1886 overwrite = expression.args.get("overwrite") 1887 1888 if isinstance(expression.this, exp.Directory): 1889 this = " OVERWRITE" if overwrite else " INTO" 1890 else: 1891 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1892 1893 stored = self.sql(expression, "stored") 1894 stored = f" {stored}" if stored else "" 1895 alternative = expression.args.get("alternative") 1896 alternative = f" OR {alternative}" if alternative else "" 1897 ignore = " IGNORE" if expression.args.get("ignore") else "" 1898 is_function = expression.args.get("is_function") 1899 if is_function: 1900 this = f"{this} FUNCTION" 1901 this = f"{this} {self.sql(expression, 'this')}" 1902 1903 exists = " IF EXISTS" if expression.args.get("exists") else "" 1904 where = self.sql(expression, "where") 1905 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1906 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1907 on_conflict = self.sql(expression, "conflict") 1908 on_conflict = f" {on_conflict}" if on_conflict else "" 1909 by_name = " BY NAME" if expression.args.get("by_name") else "" 1910 returning = self.sql(expression, "returning") 1911 1912 if self.RETURNING_END: 1913 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1914 else: 1915 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1916 1917 partition_by = self.sql(expression, "partition") 1918 partition_by = f" {partition_by}" if partition_by else "" 1919 settings = self.sql(expression, "settings") 1920 settings = f" {settings}" if settings else "" 1921 1922 source = self.sql(expression, "source") 1923 source = f"TABLE {source}" if source else "" 1924 1925 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1926 return self.prepend_ctes(expression, sql) 1927 1928 def introducer_sql(self, expression: exp.Introducer) -> str: 1929 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1930 1931 def kill_sql(self, expression: exp.Kill) -> str: 1932 kind = self.sql(expression, "kind") 1933 kind = f" {kind}" if kind else "" 1934 this = self.sql(expression, "this") 1935 this = f" {this}" if this else "" 1936 return f"KILL{kind}{this}" 1937 1938 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1939 return expression.name 1940 1941 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1942 return expression.name 1943 1944 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1945 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1946 1947 constraint = self.sql(expression, "constraint") 1948 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1949 1950 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1951 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1952 action = self.sql(expression, "action") 1953 1954 expressions = self.expressions(expression, flat=True) 1955 if expressions: 1956 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1957 expressions = f" {set_keyword}{expressions}" 1958 1959 where = self.sql(expression, "where") 1960 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1961 1962 def returning_sql(self, expression: exp.Returning) -> str: 1963 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1964 1965 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1966 fields = self.sql(expression, "fields") 1967 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1968 escaped = self.sql(expression, "escaped") 1969 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1970 items = self.sql(expression, "collection_items") 1971 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1972 keys = self.sql(expression, "map_keys") 1973 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1974 lines = self.sql(expression, "lines") 1975 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1976 null = self.sql(expression, "null") 1977 null = f" NULL DEFINED AS {null}" if null else "" 1978 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1979 1980 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1981 return f"WITH ({self.expressions(expression, flat=True)})" 1982 1983 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1984 this = f"{self.sql(expression, 'this')} INDEX" 1985 target = self.sql(expression, "target") 1986 target = f" FOR {target}" if target else "" 1987 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1988 1989 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1990 this = self.sql(expression, "this") 1991 kind = self.sql(expression, "kind") 1992 expr = self.sql(expression, "expression") 1993 return f"{this} ({kind} => {expr})" 1994 1995 def table_parts(self, expression: exp.Table) -> str: 1996 return ".".join( 1997 self.sql(part) 1998 for part in ( 1999 expression.args.get("catalog"), 2000 expression.args.get("db"), 2001 expression.args.get("this"), 2002 ) 2003 if part is not None 2004 ) 2005 2006 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2007 table = self.table_parts(expression) 2008 only = "ONLY " if expression.args.get("only") else "" 2009 partition = self.sql(expression, "partition") 2010 partition = f" {partition}" if partition else "" 2011 version = self.sql(expression, "version") 2012 version = f" {version}" if version else "" 2013 alias = self.sql(expression, "alias") 2014 alias = f"{sep}{alias}" if alias else "" 2015 2016 sample = self.sql(expression, "sample") 2017 if self.dialect.ALIAS_POST_TABLESAMPLE: 2018 sample_pre_alias = sample 2019 sample_post_alias = "" 2020 else: 2021 sample_pre_alias = "" 2022 sample_post_alias = sample 2023 2024 hints = self.expressions(expression, key="hints", sep=" ") 2025 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2026 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2027 joins = self.indent( 2028 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2029 ) 2030 laterals = self.expressions(expression, key="laterals", sep="") 2031 2032 file_format = self.sql(expression, "format") 2033 if file_format: 2034 pattern = self.sql(expression, "pattern") 2035 pattern = f", PATTERN => {pattern}" if pattern else "" 2036 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2037 2038 ordinality = expression.args.get("ordinality") or "" 2039 if ordinality: 2040 ordinality = f" WITH ORDINALITY{alias}" 2041 alias = "" 2042 2043 when = self.sql(expression, "when") 2044 if when: 2045 table = f"{table} {when}" 2046 2047 changes = self.sql(expression, "changes") 2048 changes = f" {changes}" if changes else "" 2049 2050 rows_from = self.expressions(expression, key="rows_from") 2051 if rows_from: 2052 table = f"ROWS FROM {self.wrap(rows_from)}" 2053 2054 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2055 2056 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2057 table = self.func("TABLE", expression.this) 2058 alias = self.sql(expression, "alias") 2059 alias = f" AS {alias}" if alias else "" 2060 sample = self.sql(expression, "sample") 2061 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2062 joins = self.indent( 2063 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2064 ) 2065 return f"{table}{alias}{pivots}{sample}{joins}" 2066 2067 def tablesample_sql( 2068 self, 2069 expression: exp.TableSample, 2070 tablesample_keyword: t.Optional[str] = None, 2071 ) -> str: 2072 method = self.sql(expression, "method") 2073 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2074 numerator = self.sql(expression, "bucket_numerator") 2075 denominator = self.sql(expression, "bucket_denominator") 2076 field = self.sql(expression, "bucket_field") 2077 field = f" ON {field}" if field else "" 2078 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2079 seed = self.sql(expression, "seed") 2080 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2081 2082 size = self.sql(expression, "size") 2083 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2084 size = f"{size} ROWS" 2085 2086 percent = self.sql(expression, "percent") 2087 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2088 percent = f"{percent} PERCENT" 2089 2090 expr = f"{bucket}{percent}{size}" 2091 if self.TABLESAMPLE_REQUIRES_PARENS: 2092 expr = f"({expr})" 2093 2094 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2095 2096 def pivot_sql(self, expression: exp.Pivot) -> str: 2097 expressions = self.expressions(expression, flat=True) 2098 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2099 2100 group = self.sql(expression, "group") 2101 2102 if expression.this: 2103 this = self.sql(expression, "this") 2104 if not expressions: 2105 return f"UNPIVOT {this}" 2106 2107 on = f"{self.seg('ON')} {expressions}" 2108 into = self.sql(expression, "into") 2109 into = f"{self.seg('INTO')} {into}" if into else "" 2110 using = self.expressions(expression, key="using", flat=True) 2111 using = f"{self.seg('USING')} {using}" if using else "" 2112 return f"{direction} {this}{on}{into}{using}{group}" 2113 2114 alias = self.sql(expression, "alias") 2115 alias = f" AS {alias}" if alias else "" 2116 2117 fields = self.expressions( 2118 expression, 2119 "fields", 2120 sep=" ", 2121 dynamic=True, 2122 new_line=True, 2123 skip_first=True, 2124 skip_last=True, 2125 ) 2126 2127 include_nulls = expression.args.get("include_nulls") 2128 if include_nulls is not None: 2129 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2130 else: 2131 nulls = "" 2132 2133 default_on_null = self.sql(expression, "default_on_null") 2134 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2135 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2136 2137 def version_sql(self, expression: exp.Version) -> str: 2138 this = f"FOR {expression.name}" 2139 kind = expression.text("kind") 2140 expr = self.sql(expression, "expression") 2141 return f"{this} {kind} {expr}" 2142 2143 def tuple_sql(self, expression: exp.Tuple) -> str: 2144 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2145 2146 def update_sql(self, expression: exp.Update) -> str: 2147 this = self.sql(expression, "this") 2148 set_sql = self.expressions(expression, flat=True) 2149 from_sql = self.sql(expression, "from") 2150 where_sql = self.sql(expression, "where") 2151 returning = self.sql(expression, "returning") 2152 order = self.sql(expression, "order") 2153 limit = self.sql(expression, "limit") 2154 if self.RETURNING_END: 2155 expression_sql = f"{from_sql}{where_sql}{returning}" 2156 else: 2157 expression_sql = f"{returning}{from_sql}{where_sql}" 2158 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2159 return self.prepend_ctes(expression, sql) 2160 2161 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2162 values_as_table = values_as_table and self.VALUES_AS_TABLE 2163 2164 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2165 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2166 args = self.expressions(expression) 2167 alias = self.sql(expression, "alias") 2168 values = f"VALUES{self.seg('')}{args}" 2169 values = ( 2170 f"({values})" 2171 if self.WRAP_DERIVED_VALUES 2172 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2173 else values 2174 ) 2175 return f"{values} AS {alias}" if alias else values 2176 2177 # Converts `VALUES...` expression into a series of select unions. 2178 alias_node = expression.args.get("alias") 2179 column_names = alias_node and alias_node.columns 2180 2181 selects: t.List[exp.Query] = [] 2182 2183 for i, tup in enumerate(expression.expressions): 2184 row = tup.expressions 2185 2186 if i == 0 and column_names: 2187 row = [ 2188 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2189 ] 2190 2191 selects.append(exp.Select(expressions=row)) 2192 2193 if self.pretty: 2194 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2195 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2196 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2197 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2198 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2199 2200 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2201 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2202 return f"({unions}){alias}" 2203 2204 def var_sql(self, expression: exp.Var) -> str: 2205 return self.sql(expression, "this") 2206 2207 @unsupported_args("expressions") 2208 def into_sql(self, expression: exp.Into) -> str: 2209 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2210 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2211 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2212 2213 def from_sql(self, expression: exp.From) -> str: 2214 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2215 2216 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2217 grouping_sets = self.expressions(expression, indent=False) 2218 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2219 2220 def rollup_sql(self, expression: exp.Rollup) -> str: 2221 expressions = self.expressions(expression, indent=False) 2222 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2223 2224 def cube_sql(self, expression: exp.Cube) -> str: 2225 expressions = self.expressions(expression, indent=False) 2226 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2227 2228 def group_sql(self, expression: exp.Group) -> str: 2229 group_by_all = expression.args.get("all") 2230 if group_by_all is True: 2231 modifier = " ALL" 2232 elif group_by_all is False: 2233 modifier = " DISTINCT" 2234 else: 2235 modifier = "" 2236 2237 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2238 2239 grouping_sets = self.expressions(expression, key="grouping_sets") 2240 cube = self.expressions(expression, key="cube") 2241 rollup = self.expressions(expression, key="rollup") 2242 2243 groupings = csv( 2244 self.seg(grouping_sets) if grouping_sets else "", 2245 self.seg(cube) if cube else "", 2246 self.seg(rollup) if rollup else "", 2247 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2248 sep=self.GROUPINGS_SEP, 2249 ) 2250 2251 if ( 2252 expression.expressions 2253 and groupings 2254 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2255 ): 2256 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2257 2258 return f"{group_by}{groupings}" 2259 2260 def having_sql(self, expression: exp.Having) -> str: 2261 this = self.indent(self.sql(expression, "this")) 2262 return f"{self.seg('HAVING')}{self.sep()}{this}" 2263 2264 def connect_sql(self, expression: exp.Connect) -> str: 2265 start = self.sql(expression, "start") 2266 start = self.seg(f"START WITH {start}") if start else "" 2267 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2268 connect = self.sql(expression, "connect") 2269 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2270 return start + connect 2271 2272 def prior_sql(self, expression: exp.Prior) -> str: 2273 return f"PRIOR {self.sql(expression, 'this')}" 2274 2275 def join_sql(self, expression: exp.Join) -> str: 2276 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2277 side = None 2278 else: 2279 side = expression.side 2280 2281 op_sql = " ".join( 2282 op 2283 for op in ( 2284 expression.method, 2285 "GLOBAL" if expression.args.get("global") else None, 2286 side, 2287 expression.kind, 2288 expression.hint if self.JOIN_HINTS else None, 2289 ) 2290 if op 2291 ) 2292 match_cond = self.sql(expression, "match_condition") 2293 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2294 on_sql = self.sql(expression, "on") 2295 using = expression.args.get("using") 2296 2297 if not on_sql and using: 2298 on_sql = csv(*(self.sql(column) for column in using)) 2299 2300 this = expression.this 2301 this_sql = self.sql(this) 2302 2303 exprs = self.expressions(expression) 2304 if exprs: 2305 this_sql = f"{this_sql},{self.seg(exprs)}" 2306 2307 if on_sql: 2308 on_sql = self.indent(on_sql, skip_first=True) 2309 space = self.seg(" " * self.pad) if self.pretty else " " 2310 if using: 2311 on_sql = f"{space}USING ({on_sql})" 2312 else: 2313 on_sql = f"{space}ON {on_sql}" 2314 elif not op_sql: 2315 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2316 return f" {this_sql}" 2317 2318 return f", {this_sql}" 2319 2320 if op_sql != "STRAIGHT_JOIN": 2321 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2322 2323 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2324 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2325 2326 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2327 args = self.expressions(expression, flat=True) 2328 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2329 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2330 2331 def lateral_op(self, expression: exp.Lateral) -> str: 2332 cross_apply = expression.args.get("cross_apply") 2333 2334 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2335 if cross_apply is True: 2336 op = "INNER JOIN " 2337 elif cross_apply is False: 2338 op = "LEFT JOIN " 2339 else: 2340 op = "" 2341 2342 return f"{op}LATERAL" 2343 2344 def lateral_sql(self, expression: exp.Lateral) -> str: 2345 this = self.sql(expression, "this") 2346 2347 if expression.args.get("view"): 2348 alias = expression.args["alias"] 2349 columns = self.expressions(alias, key="columns", flat=True) 2350 table = f" {alias.name}" if alias.name else "" 2351 columns = f" AS {columns}" if columns else "" 2352 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2353 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2354 2355 alias = self.sql(expression, "alias") 2356 alias = f" AS {alias}" if alias else "" 2357 2358 ordinality = expression.args.get("ordinality") or "" 2359 if ordinality: 2360 ordinality = f" WITH ORDINALITY{alias}" 2361 alias = "" 2362 2363 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2364 2365 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2366 this = self.sql(expression, "this") 2367 2368 args = [ 2369 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2370 for e in (expression.args.get(k) for k in ("offset", "expression")) 2371 if e 2372 ] 2373 2374 args_sql = ", ".join(self.sql(e) for e in args) 2375 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2376 expressions = self.expressions(expression, flat=True) 2377 limit_options = self.sql(expression, "limit_options") 2378 expressions = f" BY {expressions}" if expressions else "" 2379 2380 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2381 2382 def offset_sql(self, expression: exp.Offset) -> str: 2383 this = self.sql(expression, "this") 2384 value = expression.expression 2385 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2386 expressions = self.expressions(expression, flat=True) 2387 expressions = f" BY {expressions}" if expressions else "" 2388 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2389 2390 def setitem_sql(self, expression: exp.SetItem) -> str: 2391 kind = self.sql(expression, "kind") 2392 kind = f"{kind} " if kind else "" 2393 this = self.sql(expression, "this") 2394 expressions = self.expressions(expression) 2395 collate = self.sql(expression, "collate") 2396 collate = f" COLLATE {collate}" if collate else "" 2397 global_ = "GLOBAL " if expression.args.get("global") else "" 2398 return f"{global_}{kind}{this}{expressions}{collate}" 2399 2400 def set_sql(self, expression: exp.Set) -> str: 2401 expressions = f" {self.expressions(expression, flat=True)}" 2402 tag = " TAG" if expression.args.get("tag") else "" 2403 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2404 2405 def pragma_sql(self, expression: exp.Pragma) -> str: 2406 return f"PRAGMA {self.sql(expression, 'this')}" 2407 2408 def lock_sql(self, expression: exp.Lock) -> str: 2409 if not self.LOCKING_READS_SUPPORTED: 2410 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2411 return "" 2412 2413 update = expression.args["update"] 2414 key = expression.args.get("key") 2415 if update: 2416 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2417 else: 2418 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2419 expressions = self.expressions(expression, flat=True) 2420 expressions = f" OF {expressions}" if expressions else "" 2421 wait = expression.args.get("wait") 2422 2423 if wait is not None: 2424 if isinstance(wait, exp.Literal): 2425 wait = f" WAIT {self.sql(wait)}" 2426 else: 2427 wait = " NOWAIT" if wait else " SKIP LOCKED" 2428 2429 return f"{lock_type}{expressions}{wait or ''}" 2430 2431 def literal_sql(self, expression: exp.Literal) -> str: 2432 text = expression.this or "" 2433 if expression.is_string: 2434 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2435 return text 2436 2437 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2438 if self.dialect.ESCAPED_SEQUENCES: 2439 to_escaped = self.dialect.ESCAPED_SEQUENCES 2440 text = "".join( 2441 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2442 ) 2443 2444 return self._replace_line_breaks(text).replace( 2445 self.dialect.QUOTE_END, self._escaped_quote_end 2446 ) 2447 2448 def loaddata_sql(self, expression: exp.LoadData) -> str: 2449 local = " LOCAL" if expression.args.get("local") else "" 2450 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2451 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2452 this = f" INTO TABLE {self.sql(expression, 'this')}" 2453 partition = self.sql(expression, "partition") 2454 partition = f" {partition}" if partition else "" 2455 input_format = self.sql(expression, "input_format") 2456 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2457 serde = self.sql(expression, "serde") 2458 serde = f" SERDE {serde}" if serde else "" 2459 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2460 2461 def null_sql(self, *_) -> str: 2462 return "NULL" 2463 2464 def boolean_sql(self, expression: exp.Boolean) -> str: 2465 return "TRUE" if expression.this else "FALSE" 2466 2467 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2468 this = self.sql(expression, "this") 2469 this = f"{this} " if this else this 2470 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2471 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2472 2473 def withfill_sql(self, expression: exp.WithFill) -> str: 2474 from_sql = self.sql(expression, "from") 2475 from_sql = f" FROM {from_sql}" if from_sql else "" 2476 to_sql = self.sql(expression, "to") 2477 to_sql = f" TO {to_sql}" if to_sql else "" 2478 step_sql = self.sql(expression, "step") 2479 step_sql = f" STEP {step_sql}" if step_sql else "" 2480 interpolated_values = [ 2481 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2482 if isinstance(e, exp.Alias) 2483 else self.sql(e, "this") 2484 for e in expression.args.get("interpolate") or [] 2485 ] 2486 interpolate = ( 2487 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2488 ) 2489 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2490 2491 def cluster_sql(self, expression: exp.Cluster) -> str: 2492 return self.op_expressions("CLUSTER BY", expression) 2493 2494 def distribute_sql(self, expression: exp.Distribute) -> str: 2495 return self.op_expressions("DISTRIBUTE BY", expression) 2496 2497 def sort_sql(self, expression: exp.Sort) -> str: 2498 return self.op_expressions("SORT BY", expression) 2499 2500 def ordered_sql(self, expression: exp.Ordered) -> str: 2501 desc = expression.args.get("desc") 2502 asc = not desc 2503 2504 nulls_first = expression.args.get("nulls_first") 2505 nulls_last = not nulls_first 2506 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2507 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2508 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2509 2510 this = self.sql(expression, "this") 2511 2512 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2513 nulls_sort_change = "" 2514 if nulls_first and ( 2515 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2516 ): 2517 nulls_sort_change = " NULLS FIRST" 2518 elif ( 2519 nulls_last 2520 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2521 and not nulls_are_last 2522 ): 2523 nulls_sort_change = " NULLS LAST" 2524 2525 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2526 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2527 window = expression.find_ancestor(exp.Window, exp.Select) 2528 if isinstance(window, exp.Window) and window.args.get("spec"): 2529 self.unsupported( 2530 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2531 ) 2532 nulls_sort_change = "" 2533 elif self.NULL_ORDERING_SUPPORTED is False and ( 2534 (asc and nulls_sort_change == " NULLS LAST") 2535 or (desc and nulls_sort_change == " NULLS FIRST") 2536 ): 2537 # BigQuery does not allow these ordering/nulls combinations when used under 2538 # an aggregation func or under a window containing one 2539 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2540 2541 if isinstance(ancestor, exp.Window): 2542 ancestor = ancestor.this 2543 if isinstance(ancestor, exp.AggFunc): 2544 self.unsupported( 2545 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2546 ) 2547 nulls_sort_change = "" 2548 elif self.NULL_ORDERING_SUPPORTED is None: 2549 if expression.this.is_int: 2550 self.unsupported( 2551 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2552 ) 2553 elif not isinstance(expression.this, exp.Rand): 2554 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2555 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2556 nulls_sort_change = "" 2557 2558 with_fill = self.sql(expression, "with_fill") 2559 with_fill = f" {with_fill}" if with_fill else "" 2560 2561 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2562 2563 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2564 window_frame = self.sql(expression, "window_frame") 2565 window_frame = f"{window_frame} " if window_frame else "" 2566 2567 this = self.sql(expression, "this") 2568 2569 return f"{window_frame}{this}" 2570 2571 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2572 partition = self.partition_by_sql(expression) 2573 order = self.sql(expression, "order") 2574 measures = self.expressions(expression, key="measures") 2575 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2576 rows = self.sql(expression, "rows") 2577 rows = self.seg(rows) if rows else "" 2578 after = self.sql(expression, "after") 2579 after = self.seg(after) if after else "" 2580 pattern = self.sql(expression, "pattern") 2581 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2582 definition_sqls = [ 2583 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2584 for definition in expression.args.get("define", []) 2585 ] 2586 definitions = self.expressions(sqls=definition_sqls) 2587 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2588 body = "".join( 2589 ( 2590 partition, 2591 order, 2592 measures, 2593 rows, 2594 after, 2595 pattern, 2596 define, 2597 ) 2598 ) 2599 alias = self.sql(expression, "alias") 2600 alias = f" {alias}" if alias else "" 2601 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2602 2603 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2604 limit = expression.args.get("limit") 2605 2606 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2607 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2608 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2609 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2610 2611 return csv( 2612 *sqls, 2613 *[self.sql(join) for join in expression.args.get("joins") or []], 2614 self.sql(expression, "match"), 2615 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2616 self.sql(expression, "prewhere"), 2617 self.sql(expression, "where"), 2618 self.sql(expression, "connect"), 2619 self.sql(expression, "group"), 2620 self.sql(expression, "having"), 2621 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2622 self.sql(expression, "order"), 2623 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2624 *self.after_limit_modifiers(expression), 2625 self.options_modifier(expression), 2626 self.for_modifiers(expression), 2627 sep="", 2628 ) 2629 2630 def options_modifier(self, expression: exp.Expression) -> str: 2631 options = self.expressions(expression, key="options") 2632 return f" {options}" if options else "" 2633 2634 def for_modifiers(self, expression: exp.Expression) -> str: 2635 for_modifiers = self.expressions(expression, key="for") 2636 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2637 2638 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2639 self.unsupported("Unsupported query option.") 2640 return "" 2641 2642 def offset_limit_modifiers( 2643 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2644 ) -> t.List[str]: 2645 return [ 2646 self.sql(expression, "offset") if fetch else self.sql(limit), 2647 self.sql(limit) if fetch else self.sql(expression, "offset"), 2648 ] 2649 2650 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2651 locks = self.expressions(expression, key="locks", sep=" ") 2652 locks = f" {locks}" if locks else "" 2653 return [locks, self.sql(expression, "sample")] 2654 2655 def select_sql(self, expression: exp.Select) -> str: 2656 into = expression.args.get("into") 2657 if not self.SUPPORTS_SELECT_INTO and into: 2658 into.pop() 2659 2660 hint = self.sql(expression, "hint") 2661 distinct = self.sql(expression, "distinct") 2662 distinct = f" {distinct}" if distinct else "" 2663 kind = self.sql(expression, "kind") 2664 2665 limit = expression.args.get("limit") 2666 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2667 top = self.limit_sql(limit, top=True) 2668 limit.pop() 2669 else: 2670 top = "" 2671 2672 expressions = self.expressions(expression) 2673 2674 if kind: 2675 if kind in self.SELECT_KINDS: 2676 kind = f" AS {kind}" 2677 else: 2678 if kind == "STRUCT": 2679 expressions = self.expressions( 2680 sqls=[ 2681 self.sql( 2682 exp.Struct( 2683 expressions=[ 2684 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2685 if isinstance(e, exp.Alias) 2686 else e 2687 for e in expression.expressions 2688 ] 2689 ) 2690 ) 2691 ] 2692 ) 2693 kind = "" 2694 2695 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2696 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2697 2698 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2699 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2700 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2701 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2702 sql = self.query_modifiers( 2703 expression, 2704 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2705 self.sql(expression, "into", comment=False), 2706 self.sql(expression, "from", comment=False), 2707 ) 2708 2709 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2710 if expression.args.get("with"): 2711 sql = self.maybe_comment(sql, expression) 2712 expression.pop_comments() 2713 2714 sql = self.prepend_ctes(expression, sql) 2715 2716 if not self.SUPPORTS_SELECT_INTO and into: 2717 if into.args.get("temporary"): 2718 table_kind = " TEMPORARY" 2719 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2720 table_kind = " UNLOGGED" 2721 else: 2722 table_kind = "" 2723 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2724 2725 return sql 2726 2727 def schema_sql(self, expression: exp.Schema) -> str: 2728 this = self.sql(expression, "this") 2729 sql = self.schema_columns_sql(expression) 2730 return f"{this} {sql}" if this and sql else this or sql 2731 2732 def schema_columns_sql(self, expression: exp.Schema) -> str: 2733 if expression.expressions: 2734 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2735 return "" 2736 2737 def star_sql(self, expression: exp.Star) -> str: 2738 except_ = self.expressions(expression, key="except", flat=True) 2739 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2740 replace = self.expressions(expression, key="replace", flat=True) 2741 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2742 rename = self.expressions(expression, key="rename", flat=True) 2743 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2744 return f"*{except_}{replace}{rename}" 2745 2746 def parameter_sql(self, expression: exp.Parameter) -> str: 2747 this = self.sql(expression, "this") 2748 return f"{self.PARAMETER_TOKEN}{this}" 2749 2750 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2751 this = self.sql(expression, "this") 2752 kind = expression.text("kind") 2753 if kind: 2754 kind = f"{kind}." 2755 return f"@@{kind}{this}" 2756 2757 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2758 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2759 2760 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2761 alias = self.sql(expression, "alias") 2762 alias = f"{sep}{alias}" if alias else "" 2763 sample = self.sql(expression, "sample") 2764 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2765 alias = f"{sample}{alias}" 2766 2767 # Set to None so it's not generated again by self.query_modifiers() 2768 expression.set("sample", None) 2769 2770 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2771 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2772 return self.prepend_ctes(expression, sql) 2773 2774 def qualify_sql(self, expression: exp.Qualify) -> str: 2775 this = self.indent(self.sql(expression, "this")) 2776 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2777 2778 def unnest_sql(self, expression: exp.Unnest) -> str: 2779 args = self.expressions(expression, flat=True) 2780 2781 alias = expression.args.get("alias") 2782 offset = expression.args.get("offset") 2783 2784 if self.UNNEST_WITH_ORDINALITY: 2785 if alias and isinstance(offset, exp.Expression): 2786 alias.append("columns", offset) 2787 2788 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2789 columns = alias.columns 2790 alias = self.sql(columns[0]) if columns else "" 2791 else: 2792 alias = self.sql(alias) 2793 2794 alias = f" AS {alias}" if alias else alias 2795 if self.UNNEST_WITH_ORDINALITY: 2796 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2797 else: 2798 if isinstance(offset, exp.Expression): 2799 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2800 elif offset: 2801 suffix = f"{alias} WITH OFFSET" 2802 else: 2803 suffix = alias 2804 2805 return f"UNNEST({args}){suffix}" 2806 2807 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2808 return "" 2809 2810 def where_sql(self, expression: exp.Where) -> str: 2811 this = self.indent(self.sql(expression, "this")) 2812 return f"{self.seg('WHERE')}{self.sep()}{this}" 2813 2814 def window_sql(self, expression: exp.Window) -> str: 2815 this = self.sql(expression, "this") 2816 partition = self.partition_by_sql(expression) 2817 order = expression.args.get("order") 2818 order = self.order_sql(order, flat=True) if order else "" 2819 spec = self.sql(expression, "spec") 2820 alias = self.sql(expression, "alias") 2821 over = self.sql(expression, "over") or "OVER" 2822 2823 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2824 2825 first = expression.args.get("first") 2826 if first is None: 2827 first = "" 2828 else: 2829 first = "FIRST" if first else "LAST" 2830 2831 if not partition and not order and not spec and alias: 2832 return f"{this} {alias}" 2833 2834 args = self.format_args( 2835 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2836 ) 2837 return f"{this} ({args})" 2838 2839 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2840 partition = self.expressions(expression, key="partition_by", flat=True) 2841 return f"PARTITION BY {partition}" if partition else "" 2842 2843 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2844 kind = self.sql(expression, "kind") 2845 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2846 end = ( 2847 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2848 or "CURRENT ROW" 2849 ) 2850 2851 window_spec = f"{kind} BETWEEN {start} AND {end}" 2852 2853 exclude = self.sql(expression, "exclude") 2854 if exclude: 2855 if self.SUPPORTS_WINDOW_EXCLUDE: 2856 window_spec += f" EXCLUDE {exclude}" 2857 else: 2858 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2859 2860 return window_spec 2861 2862 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2863 this = self.sql(expression, "this") 2864 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2865 return f"{this} WITHIN GROUP ({expression_sql})" 2866 2867 def between_sql(self, expression: exp.Between) -> str: 2868 this = self.sql(expression, "this") 2869 low = self.sql(expression, "low") 2870 high = self.sql(expression, "high") 2871 symmetric = expression.args.get("symmetric") 2872 2873 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2874 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2875 2876 flag = ( 2877 " SYMMETRIC" 2878 if symmetric 2879 else " ASYMMETRIC" 2880 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2881 else "" # silently drop ASYMMETRIC – semantics identical 2882 ) 2883 return f"{this} BETWEEN{flag} {low} AND {high}" 2884 2885 def bracket_offset_expressions( 2886 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2887 ) -> t.List[exp.Expression]: 2888 return apply_index_offset( 2889 expression.this, 2890 expression.expressions, 2891 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2892 dialect=self.dialect, 2893 ) 2894 2895 def bracket_sql(self, expression: exp.Bracket) -> str: 2896 expressions = self.bracket_offset_expressions(expression) 2897 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2898 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2899 2900 def all_sql(self, expression: exp.All) -> str: 2901 return f"ALL {self.wrap(expression)}" 2902 2903 def any_sql(self, expression: exp.Any) -> str: 2904 this = self.sql(expression, "this") 2905 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2906 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2907 this = self.wrap(this) 2908 return f"ANY{this}" 2909 return f"ANY {this}" 2910 2911 def exists_sql(self, expression: exp.Exists) -> str: 2912 return f"EXISTS{self.wrap(expression)}" 2913 2914 def case_sql(self, expression: exp.Case) -> str: 2915 this = self.sql(expression, "this") 2916 statements = [f"CASE {this}" if this else "CASE"] 2917 2918 for e in expression.args["ifs"]: 2919 statements.append(f"WHEN {self.sql(e, 'this')}") 2920 statements.append(f"THEN {self.sql(e, 'true')}") 2921 2922 default = self.sql(expression, "default") 2923 2924 if default: 2925 statements.append(f"ELSE {default}") 2926 2927 statements.append("END") 2928 2929 if self.pretty and self.too_wide(statements): 2930 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2931 2932 return " ".join(statements) 2933 2934 def constraint_sql(self, expression: exp.Constraint) -> str: 2935 this = self.sql(expression, "this") 2936 expressions = self.expressions(expression, flat=True) 2937 return f"CONSTRAINT {this} {expressions}" 2938 2939 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2940 order = expression.args.get("order") 2941 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2942 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2943 2944 def extract_sql(self, expression: exp.Extract) -> str: 2945 from sqlglot.dialects.dialect import map_date_part 2946 2947 this = ( 2948 map_date_part(expression.this, self.dialect) 2949 if self.NORMALIZE_EXTRACT_DATE_PARTS 2950 else expression.this 2951 ) 2952 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2953 expression_sql = self.sql(expression, "expression") 2954 2955 return f"EXTRACT({this_sql} FROM {expression_sql})" 2956 2957 def trim_sql(self, expression: exp.Trim) -> str: 2958 trim_type = self.sql(expression, "position") 2959 2960 if trim_type == "LEADING": 2961 func_name = "LTRIM" 2962 elif trim_type == "TRAILING": 2963 func_name = "RTRIM" 2964 else: 2965 func_name = "TRIM" 2966 2967 return self.func(func_name, expression.this, expression.expression) 2968 2969 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2970 args = expression.expressions 2971 if isinstance(expression, exp.ConcatWs): 2972 args = args[1:] # Skip the delimiter 2973 2974 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2975 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2976 2977 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2978 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2979 2980 return args 2981 2982 def concat_sql(self, expression: exp.Concat) -> str: 2983 expressions = self.convert_concat_args(expression) 2984 2985 # Some dialects don't allow a single-argument CONCAT call 2986 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2987 return self.sql(expressions[0]) 2988 2989 return self.func("CONCAT", *expressions) 2990 2991 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2992 return self.func( 2993 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2994 ) 2995 2996 def check_sql(self, expression: exp.Check) -> str: 2997 this = self.sql(expression, key="this") 2998 return f"CHECK ({this})" 2999 3000 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3001 expressions = self.expressions(expression, flat=True) 3002 expressions = f" ({expressions})" if expressions else "" 3003 reference = self.sql(expression, "reference") 3004 reference = f" {reference}" if reference else "" 3005 delete = self.sql(expression, "delete") 3006 delete = f" ON DELETE {delete}" if delete else "" 3007 update = self.sql(expression, "update") 3008 update = f" ON UPDATE {update}" if update else "" 3009 options = self.expressions(expression, key="options", flat=True, sep=" ") 3010 options = f" {options}" if options else "" 3011 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3012 3013 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3014 expressions = self.expressions(expression, flat=True) 3015 include = self.sql(expression, "include") 3016 options = self.expressions(expression, key="options", flat=True, sep=" ") 3017 options = f" {options}" if options else "" 3018 return f"PRIMARY KEY ({expressions}){include}{options}" 3019 3020 def if_sql(self, expression: exp.If) -> str: 3021 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3022 3023 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3024 modifier = expression.args.get("modifier") 3025 modifier = f" {modifier}" if modifier else "" 3026 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3027 3028 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3029 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3030 3031 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3032 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3033 3034 if expression.args.get("escape"): 3035 path = self.escape_str(path) 3036 3037 if self.QUOTE_JSON_PATH: 3038 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3039 3040 return path 3041 3042 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3043 if isinstance(expression, exp.JSONPathPart): 3044 transform = self.TRANSFORMS.get(expression.__class__) 3045 if not callable(transform): 3046 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3047 return "" 3048 3049 return transform(self, expression) 3050 3051 if isinstance(expression, int): 3052 return str(expression) 3053 3054 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3055 escaped = expression.replace("'", "\\'") 3056 escaped = f"\\'{expression}\\'" 3057 else: 3058 escaped = expression.replace('"', '\\"') 3059 escaped = f'"{escaped}"' 3060 3061 return escaped 3062 3063 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3064 return f"{self.sql(expression, 'this')} FORMAT JSON" 3065 3066 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3067 # Output the Teradata column FORMAT override. 3068 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3069 this = self.sql(expression, "this") 3070 fmt = self.sql(expression, "format") 3071 return f"{this} (FORMAT {fmt})" 3072 3073 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3074 null_handling = expression.args.get("null_handling") 3075 null_handling = f" {null_handling}" if null_handling else "" 3076 3077 unique_keys = expression.args.get("unique_keys") 3078 if unique_keys is not None: 3079 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3080 else: 3081 unique_keys = "" 3082 3083 return_type = self.sql(expression, "return_type") 3084 return_type = f" RETURNING {return_type}" if return_type else "" 3085 encoding = self.sql(expression, "encoding") 3086 encoding = f" ENCODING {encoding}" if encoding else "" 3087 3088 return self.func( 3089 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3090 *expression.expressions, 3091 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3092 ) 3093 3094 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3095 return self.jsonobject_sql(expression) 3096 3097 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3098 null_handling = expression.args.get("null_handling") 3099 null_handling = f" {null_handling}" if null_handling else "" 3100 return_type = self.sql(expression, "return_type") 3101 return_type = f" RETURNING {return_type}" if return_type else "" 3102 strict = " STRICT" if expression.args.get("strict") else "" 3103 return self.func( 3104 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3105 ) 3106 3107 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3108 this = self.sql(expression, "this") 3109 order = self.sql(expression, "order") 3110 null_handling = expression.args.get("null_handling") 3111 null_handling = f" {null_handling}" if null_handling else "" 3112 return_type = self.sql(expression, "return_type") 3113 return_type = f" RETURNING {return_type}" if return_type else "" 3114 strict = " STRICT" if expression.args.get("strict") else "" 3115 return self.func( 3116 "JSON_ARRAYAGG", 3117 this, 3118 suffix=f"{order}{null_handling}{return_type}{strict})", 3119 ) 3120 3121 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3122 path = self.sql(expression, "path") 3123 path = f" PATH {path}" if path else "" 3124 nested_schema = self.sql(expression, "nested_schema") 3125 3126 if nested_schema: 3127 return f"NESTED{path} {nested_schema}" 3128 3129 this = self.sql(expression, "this") 3130 kind = self.sql(expression, "kind") 3131 kind = f" {kind}" if kind else "" 3132 return f"{this}{kind}{path}" 3133 3134 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3135 return self.func("COLUMNS", *expression.expressions) 3136 3137 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3138 this = self.sql(expression, "this") 3139 path = self.sql(expression, "path") 3140 path = f", {path}" if path else "" 3141 error_handling = expression.args.get("error_handling") 3142 error_handling = f" {error_handling}" if error_handling else "" 3143 empty_handling = expression.args.get("empty_handling") 3144 empty_handling = f" {empty_handling}" if empty_handling else "" 3145 schema = self.sql(expression, "schema") 3146 return self.func( 3147 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3148 ) 3149 3150 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3151 this = self.sql(expression, "this") 3152 kind = self.sql(expression, "kind") 3153 path = self.sql(expression, "path") 3154 path = f" {path}" if path else "" 3155 as_json = " AS JSON" if expression.args.get("as_json") else "" 3156 return f"{this} {kind}{path}{as_json}" 3157 3158 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3159 this = self.sql(expression, "this") 3160 path = self.sql(expression, "path") 3161 path = f", {path}" if path else "" 3162 expressions = self.expressions(expression) 3163 with_ = ( 3164 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3165 if expressions 3166 else "" 3167 ) 3168 return f"OPENJSON({this}{path}){with_}" 3169 3170 def in_sql(self, expression: exp.In) -> str: 3171 query = expression.args.get("query") 3172 unnest = expression.args.get("unnest") 3173 field = expression.args.get("field") 3174 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3175 3176 if query: 3177 in_sql = self.sql(query) 3178 elif unnest: 3179 in_sql = self.in_unnest_op(unnest) 3180 elif field: 3181 in_sql = self.sql(field) 3182 else: 3183 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3184 3185 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3186 3187 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3188 return f"(SELECT {self.sql(unnest)})" 3189 3190 def interval_sql(self, expression: exp.Interval) -> str: 3191 unit = self.sql(expression, "unit") 3192 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3193 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3194 unit = f" {unit}" if unit else "" 3195 3196 if self.SINGLE_STRING_INTERVAL: 3197 this = expression.this.name if expression.this else "" 3198 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3199 3200 this = self.sql(expression, "this") 3201 if this: 3202 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3203 this = f" {this}" if unwrapped else f" ({this})" 3204 3205 return f"INTERVAL{this}{unit}" 3206 3207 def return_sql(self, expression: exp.Return) -> str: 3208 return f"RETURN {self.sql(expression, 'this')}" 3209 3210 def reference_sql(self, expression: exp.Reference) -> str: 3211 this = self.sql(expression, "this") 3212 expressions = self.expressions(expression, flat=True) 3213 expressions = f"({expressions})" if expressions else "" 3214 options = self.expressions(expression, key="options", flat=True, sep=" ") 3215 options = f" {options}" if options else "" 3216 return f"REFERENCES {this}{expressions}{options}" 3217 3218 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3219 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3220 parent = expression.parent 3221 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3222 return self.func( 3223 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3224 ) 3225 3226 def paren_sql(self, expression: exp.Paren) -> str: 3227 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3228 return f"({sql}{self.seg(')', sep='')}" 3229 3230 def neg_sql(self, expression: exp.Neg) -> str: 3231 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3232 this_sql = self.sql(expression, "this") 3233 sep = " " if this_sql[0] == "-" else "" 3234 return f"-{sep}{this_sql}" 3235 3236 def not_sql(self, expression: exp.Not) -> str: 3237 return f"NOT {self.sql(expression, 'this')}" 3238 3239 def alias_sql(self, expression: exp.Alias) -> str: 3240 alias = self.sql(expression, "alias") 3241 alias = f" AS {alias}" if alias else "" 3242 return f"{self.sql(expression, 'this')}{alias}" 3243 3244 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3245 alias = expression.args["alias"] 3246 3247 parent = expression.parent 3248 pivot = parent and parent.parent 3249 3250 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3251 identifier_alias = isinstance(alias, exp.Identifier) 3252 literal_alias = isinstance(alias, exp.Literal) 3253 3254 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3255 alias.replace(exp.Literal.string(alias.output_name)) 3256 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3257 alias.replace(exp.to_identifier(alias.output_name)) 3258 3259 return self.alias_sql(expression) 3260 3261 def aliases_sql(self, expression: exp.Aliases) -> str: 3262 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3263 3264 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3265 this = self.sql(expression, "this") 3266 index = self.sql(expression, "expression") 3267 return f"{this} AT {index}" 3268 3269 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3270 this = self.sql(expression, "this") 3271 zone = self.sql(expression, "zone") 3272 return f"{this} AT TIME ZONE {zone}" 3273 3274 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3275 this = self.sql(expression, "this") 3276 zone = self.sql(expression, "zone") 3277 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3278 3279 def add_sql(self, expression: exp.Add) -> str: 3280 return self.binary(expression, "+") 3281 3282 def and_sql( 3283 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3284 ) -> str: 3285 return self.connector_sql(expression, "AND", stack) 3286 3287 def or_sql( 3288 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3289 ) -> str: 3290 return self.connector_sql(expression, "OR", stack) 3291 3292 def xor_sql( 3293 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3294 ) -> str: 3295 return self.connector_sql(expression, "XOR", stack) 3296 3297 def connector_sql( 3298 self, 3299 expression: exp.Connector, 3300 op: str, 3301 stack: t.Optional[t.List[str | exp.Expression]] = None, 3302 ) -> str: 3303 if stack is not None: 3304 if expression.expressions: 3305 stack.append(self.expressions(expression, sep=f" {op} ")) 3306 else: 3307 stack.append(expression.right) 3308 if expression.comments and self.comments: 3309 for comment in expression.comments: 3310 if comment: 3311 op += f" /*{self.sanitize_comment(comment)}*/" 3312 stack.extend((op, expression.left)) 3313 return op 3314 3315 stack = [expression] 3316 sqls: t.List[str] = [] 3317 ops = set() 3318 3319 while stack: 3320 node = stack.pop() 3321 if isinstance(node, exp.Connector): 3322 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3323 else: 3324 sql = self.sql(node) 3325 if sqls and sqls[-1] in ops: 3326 sqls[-1] += f" {sql}" 3327 else: 3328 sqls.append(sql) 3329 3330 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3331 return sep.join(sqls) 3332 3333 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3334 return self.binary(expression, "&") 3335 3336 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3337 return self.binary(expression, "<<") 3338 3339 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3340 return f"~{self.sql(expression, 'this')}" 3341 3342 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3343 return self.binary(expression, "|") 3344 3345 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3346 return self.binary(expression, ">>") 3347 3348 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3349 return self.binary(expression, "^") 3350 3351 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3352 format_sql = self.sql(expression, "format") 3353 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3354 to_sql = self.sql(expression, "to") 3355 to_sql = f" {to_sql}" if to_sql else "" 3356 action = self.sql(expression, "action") 3357 action = f" {action}" if action else "" 3358 default = self.sql(expression, "default") 3359 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3360 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3361 3362 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3363 zone = self.sql(expression, "this") 3364 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3365 3366 def collate_sql(self, expression: exp.Collate) -> str: 3367 if self.COLLATE_IS_FUNC: 3368 return self.function_fallback_sql(expression) 3369 return self.binary(expression, "COLLATE") 3370 3371 def command_sql(self, expression: exp.Command) -> str: 3372 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3373 3374 def comment_sql(self, expression: exp.Comment) -> str: 3375 this = self.sql(expression, "this") 3376 kind = expression.args["kind"] 3377 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3378 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3379 expression_sql = self.sql(expression, "expression") 3380 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3381 3382 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3383 this = self.sql(expression, "this") 3384 delete = " DELETE" if expression.args.get("delete") else "" 3385 recompress = self.sql(expression, "recompress") 3386 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3387 to_disk = self.sql(expression, "to_disk") 3388 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3389 to_volume = self.sql(expression, "to_volume") 3390 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3391 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3392 3393 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3394 where = self.sql(expression, "where") 3395 group = self.sql(expression, "group") 3396 aggregates = self.expressions(expression, key="aggregates") 3397 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3398 3399 if not (where or group or aggregates) and len(expression.expressions) == 1: 3400 return f"TTL {self.expressions(expression, flat=True)}" 3401 3402 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3403 3404 def transaction_sql(self, expression: exp.Transaction) -> str: 3405 return "BEGIN" 3406 3407 def commit_sql(self, expression: exp.Commit) -> str: 3408 chain = expression.args.get("chain") 3409 if chain is not None: 3410 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3411 3412 return f"COMMIT{chain or ''}" 3413 3414 def rollback_sql(self, expression: exp.Rollback) -> str: 3415 savepoint = expression.args.get("savepoint") 3416 savepoint = f" TO {savepoint}" if savepoint else "" 3417 return f"ROLLBACK{savepoint}" 3418 3419 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3420 this = self.sql(expression, "this") 3421 3422 dtype = self.sql(expression, "dtype") 3423 if dtype: 3424 collate = self.sql(expression, "collate") 3425 collate = f" COLLATE {collate}" if collate else "" 3426 using = self.sql(expression, "using") 3427 using = f" USING {using}" if using else "" 3428 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3429 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3430 3431 default = self.sql(expression, "default") 3432 if default: 3433 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3434 3435 comment = self.sql(expression, "comment") 3436 if comment: 3437 return f"ALTER COLUMN {this} COMMENT {comment}" 3438 3439 visible = expression.args.get("visible") 3440 if visible: 3441 return f"ALTER COLUMN {this} SET {visible}" 3442 3443 allow_null = expression.args.get("allow_null") 3444 drop = expression.args.get("drop") 3445 3446 if not drop and not allow_null: 3447 self.unsupported("Unsupported ALTER COLUMN syntax") 3448 3449 if allow_null is not None: 3450 keyword = "DROP" if drop else "SET" 3451 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3452 3453 return f"ALTER COLUMN {this} DROP DEFAULT" 3454 3455 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3456 this = self.sql(expression, "this") 3457 3458 visible = expression.args.get("visible") 3459 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3460 3461 return f"ALTER INDEX {this} {visible_sql}" 3462 3463 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3464 this = self.sql(expression, "this") 3465 if not isinstance(expression.this, exp.Var): 3466 this = f"KEY DISTKEY {this}" 3467 return f"ALTER DISTSTYLE {this}" 3468 3469 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3470 compound = " COMPOUND" if expression.args.get("compound") else "" 3471 this = self.sql(expression, "this") 3472 expressions = self.expressions(expression, flat=True) 3473 expressions = f"({expressions})" if expressions else "" 3474 return f"ALTER{compound} SORTKEY {this or expressions}" 3475 3476 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3477 if not self.RENAME_TABLE_WITH_DB: 3478 # Remove db from tables 3479 expression = expression.transform( 3480 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3481 ).assert_is(exp.AlterRename) 3482 this = self.sql(expression, "this") 3483 return f"RENAME TO {this}" 3484 3485 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3486 exists = " IF EXISTS" if expression.args.get("exists") else "" 3487 old_column = self.sql(expression, "this") 3488 new_column = self.sql(expression, "to") 3489 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3490 3491 def alterset_sql(self, expression: exp.AlterSet) -> str: 3492 exprs = self.expressions(expression, flat=True) 3493 if self.ALTER_SET_WRAPPED: 3494 exprs = f"({exprs})" 3495 3496 return f"SET {exprs}" 3497 3498 def alter_sql(self, expression: exp.Alter) -> str: 3499 actions = expression.args["actions"] 3500 3501 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3502 actions[0], exp.ColumnDef 3503 ): 3504 actions_sql = self.expressions(expression, key="actions", flat=True) 3505 actions_sql = f"ADD {actions_sql}" 3506 else: 3507 actions_list = [] 3508 for action in actions: 3509 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3510 action_sql = self.add_column_sql(action) 3511 else: 3512 action_sql = self.sql(action) 3513 if isinstance(action, exp.Query): 3514 action_sql = f"AS {action_sql}" 3515 3516 actions_list.append(action_sql) 3517 3518 actions_sql = self.format_args(*actions_list).lstrip("\n") 3519 3520 exists = " IF EXISTS" if expression.args.get("exists") else "" 3521 on_cluster = self.sql(expression, "cluster") 3522 on_cluster = f" {on_cluster}" if on_cluster else "" 3523 only = " ONLY" if expression.args.get("only") else "" 3524 options = self.expressions(expression, key="options") 3525 options = f", {options}" if options else "" 3526 kind = self.sql(expression, "kind") 3527 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3528 3529 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3530 3531 def add_column_sql(self, expression: exp.Expression) -> str: 3532 sql = self.sql(expression) 3533 if isinstance(expression, exp.Schema): 3534 column_text = " COLUMNS" 3535 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3536 column_text = " COLUMN" 3537 else: 3538 column_text = "" 3539 3540 return f"ADD{column_text} {sql}" 3541 3542 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3543 expressions = self.expressions(expression) 3544 exists = " IF EXISTS " if expression.args.get("exists") else " " 3545 return f"DROP{exists}{expressions}" 3546 3547 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3548 return f"ADD {self.expressions(expression, indent=False)}" 3549 3550 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3551 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3552 return f"ADD {exists}{self.sql(expression.this)}" 3553 3554 def distinct_sql(self, expression: exp.Distinct) -> str: 3555 this = self.expressions(expression, flat=True) 3556 3557 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3558 case = exp.case() 3559 for arg in expression.expressions: 3560 case = case.when(arg.is_(exp.null()), exp.null()) 3561 this = self.sql(case.else_(f"({this})")) 3562 3563 this = f" {this}" if this else "" 3564 3565 on = self.sql(expression, "on") 3566 on = f" ON {on}" if on else "" 3567 return f"DISTINCT{this}{on}" 3568 3569 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3570 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3571 3572 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3573 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3574 3575 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3576 this_sql = self.sql(expression, "this") 3577 expression_sql = self.sql(expression, "expression") 3578 kind = "MAX" if expression.args.get("max") else "MIN" 3579 return f"{this_sql} HAVING {kind} {expression_sql}" 3580 3581 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3582 return self.sql( 3583 exp.Cast( 3584 this=exp.Div(this=expression.this, expression=expression.expression), 3585 to=exp.DataType(this=exp.DataType.Type.INT), 3586 ) 3587 ) 3588 3589 def dpipe_sql(self, expression: exp.DPipe) -> str: 3590 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3591 return self.func( 3592 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3593 ) 3594 return self.binary(expression, "||") 3595 3596 def div_sql(self, expression: exp.Div) -> str: 3597 l, r = expression.left, expression.right 3598 3599 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3600 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3601 3602 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3603 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3604 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3605 3606 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3607 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3608 return self.sql( 3609 exp.cast( 3610 l / r, 3611 to=exp.DataType.Type.BIGINT, 3612 ) 3613 ) 3614 3615 return self.binary(expression, "/") 3616 3617 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3618 n = exp._wrap(expression.this, exp.Binary) 3619 d = exp._wrap(expression.expression, exp.Binary) 3620 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3621 3622 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3623 return self.binary(expression, "OVERLAPS") 3624 3625 def distance_sql(self, expression: exp.Distance) -> str: 3626 return self.binary(expression, "<->") 3627 3628 def dot_sql(self, expression: exp.Dot) -> str: 3629 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3630 3631 def eq_sql(self, expression: exp.EQ) -> str: 3632 return self.binary(expression, "=") 3633 3634 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3635 return self.binary(expression, ":=") 3636 3637 def escape_sql(self, expression: exp.Escape) -> str: 3638 return self.binary(expression, "ESCAPE") 3639 3640 def glob_sql(self, expression: exp.Glob) -> str: 3641 return self.binary(expression, "GLOB") 3642 3643 def gt_sql(self, expression: exp.GT) -> str: 3644 return self.binary(expression, ">") 3645 3646 def gte_sql(self, expression: exp.GTE) -> str: 3647 return self.binary(expression, ">=") 3648 3649 def ilike_sql(self, expression: exp.ILike) -> str: 3650 return self.binary(expression, "ILIKE") 3651 3652 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3653 return self.binary(expression, "ILIKE ANY") 3654 3655 def is_sql(self, expression: exp.Is) -> str: 3656 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3657 return self.sql( 3658 expression.this if expression.expression.this else exp.not_(expression.this) 3659 ) 3660 return self.binary(expression, "IS") 3661 3662 def like_sql(self, expression: exp.Like) -> str: 3663 return self.binary(expression, "LIKE") 3664 3665 def likeany_sql(self, expression: exp.LikeAny) -> str: 3666 return self.binary(expression, "LIKE ANY") 3667 3668 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3669 return self.binary(expression, "SIMILAR TO") 3670 3671 def lt_sql(self, expression: exp.LT) -> str: 3672 return self.binary(expression, "<") 3673 3674 def lte_sql(self, expression: exp.LTE) -> str: 3675 return self.binary(expression, "<=") 3676 3677 def mod_sql(self, expression: exp.Mod) -> str: 3678 return self.binary(expression, "%") 3679 3680 def mul_sql(self, expression: exp.Mul) -> str: 3681 return self.binary(expression, "*") 3682 3683 def neq_sql(self, expression: exp.NEQ) -> str: 3684 return self.binary(expression, "<>") 3685 3686 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3687 return self.binary(expression, "IS NOT DISTINCT FROM") 3688 3689 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3690 return self.binary(expression, "IS DISTINCT FROM") 3691 3692 def slice_sql(self, expression: exp.Slice) -> str: 3693 return self.binary(expression, ":") 3694 3695 def sub_sql(self, expression: exp.Sub) -> str: 3696 return self.binary(expression, "-") 3697 3698 def trycast_sql(self, expression: exp.TryCast) -> str: 3699 return self.cast_sql(expression, safe_prefix="TRY_") 3700 3701 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3702 return self.cast_sql(expression) 3703 3704 def try_sql(self, expression: exp.Try) -> str: 3705 if not self.TRY_SUPPORTED: 3706 self.unsupported("Unsupported TRY function") 3707 return self.sql(expression, "this") 3708 3709 return self.func("TRY", expression.this) 3710 3711 def log_sql(self, expression: exp.Log) -> str: 3712 this = expression.this 3713 expr = expression.expression 3714 3715 if self.dialect.LOG_BASE_FIRST is False: 3716 this, expr = expr, this 3717 elif self.dialect.LOG_BASE_FIRST is None and expr: 3718 if this.name in ("2", "10"): 3719 return self.func(f"LOG{this.name}", expr) 3720 3721 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3722 3723 return self.func("LOG", this, expr) 3724 3725 def use_sql(self, expression: exp.Use) -> str: 3726 kind = self.sql(expression, "kind") 3727 kind = f" {kind}" if kind else "" 3728 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3729 this = f" {this}" if this else "" 3730 return f"USE{kind}{this}" 3731 3732 def binary(self, expression: exp.Binary, op: str) -> str: 3733 sqls: t.List[str] = [] 3734 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3735 binary_type = type(expression) 3736 3737 while stack: 3738 node = stack.pop() 3739 3740 if type(node) is binary_type: 3741 op_func = node.args.get("operator") 3742 if op_func: 3743 op = f"OPERATOR({self.sql(op_func)})" 3744 3745 stack.append(node.right) 3746 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3747 stack.append(node.left) 3748 else: 3749 sqls.append(self.sql(node)) 3750 3751 return "".join(sqls) 3752 3753 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3754 to_clause = self.sql(expression, "to") 3755 if to_clause: 3756 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3757 3758 return self.function_fallback_sql(expression) 3759 3760 def function_fallback_sql(self, expression: exp.Func) -> str: 3761 args = [] 3762 3763 for key in expression.arg_types: 3764 arg_value = expression.args.get(key) 3765 3766 if isinstance(arg_value, list): 3767 for value in arg_value: 3768 args.append(value) 3769 elif arg_value is not None: 3770 args.append(arg_value) 3771 3772 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3773 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3774 else: 3775 name = expression.sql_name() 3776 3777 return self.func(name, *args) 3778 3779 def func( 3780 self, 3781 name: str, 3782 *args: t.Optional[exp.Expression | str], 3783 prefix: str = "(", 3784 suffix: str = ")", 3785 normalize: bool = True, 3786 ) -> str: 3787 name = self.normalize_func(name) if normalize else name 3788 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3789 3790 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3791 arg_sqls = tuple( 3792 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3793 ) 3794 if self.pretty and self.too_wide(arg_sqls): 3795 return self.indent( 3796 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3797 ) 3798 return sep.join(arg_sqls) 3799 3800 def too_wide(self, args: t.Iterable) -> bool: 3801 return sum(len(arg) for arg in args) > self.max_text_width 3802 3803 def format_time( 3804 self, 3805 expression: exp.Expression, 3806 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3807 inverse_time_trie: t.Optional[t.Dict] = None, 3808 ) -> t.Optional[str]: 3809 return format_time( 3810 self.sql(expression, "format"), 3811 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3812 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3813 ) 3814 3815 def expressions( 3816 self, 3817 expression: t.Optional[exp.Expression] = None, 3818 key: t.Optional[str] = None, 3819 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3820 flat: bool = False, 3821 indent: bool = True, 3822 skip_first: bool = False, 3823 skip_last: bool = False, 3824 sep: str = ", ", 3825 prefix: str = "", 3826 dynamic: bool = False, 3827 new_line: bool = False, 3828 ) -> str: 3829 expressions = expression.args.get(key or "expressions") if expression else sqls 3830 3831 if not expressions: 3832 return "" 3833 3834 if flat: 3835 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3836 3837 num_sqls = len(expressions) 3838 result_sqls = [] 3839 3840 for i, e in enumerate(expressions): 3841 sql = self.sql(e, comment=False) 3842 if not sql: 3843 continue 3844 3845 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3846 3847 if self.pretty: 3848 if self.leading_comma: 3849 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3850 else: 3851 result_sqls.append( 3852 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3853 ) 3854 else: 3855 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3856 3857 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3858 if new_line: 3859 result_sqls.insert(0, "") 3860 result_sqls.append("") 3861 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3862 else: 3863 result_sql = "".join(result_sqls) 3864 3865 return ( 3866 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3867 if indent 3868 else result_sql 3869 ) 3870 3871 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3872 flat = flat or isinstance(expression.parent, exp.Properties) 3873 expressions_sql = self.expressions(expression, flat=flat) 3874 if flat: 3875 return f"{op} {expressions_sql}" 3876 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3877 3878 def naked_property(self, expression: exp.Property) -> str: 3879 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3880 if not property_name: 3881 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3882 return f"{property_name} {self.sql(expression, 'this')}" 3883 3884 def tag_sql(self, expression: exp.Tag) -> str: 3885 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3886 3887 def token_sql(self, token_type: TokenType) -> str: 3888 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3889 3890 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3891 this = self.sql(expression, "this") 3892 expressions = self.no_identify(self.expressions, expression) 3893 expressions = ( 3894 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3895 ) 3896 return f"{this}{expressions}" if expressions.strip() != "" else this 3897 3898 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3899 this = self.sql(expression, "this") 3900 expressions = self.expressions(expression, flat=True) 3901 return f"{this}({expressions})" 3902 3903 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3904 return self.binary(expression, "=>") 3905 3906 def when_sql(self, expression: exp.When) -> str: 3907 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3908 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3909 condition = self.sql(expression, "condition") 3910 condition = f" AND {condition}" if condition else "" 3911 3912 then_expression = expression.args.get("then") 3913 if isinstance(then_expression, exp.Insert): 3914 this = self.sql(then_expression, "this") 3915 this = f"INSERT {this}" if this else "INSERT" 3916 then = self.sql(then_expression, "expression") 3917 then = f"{this} VALUES {then}" if then else this 3918 elif isinstance(then_expression, exp.Update): 3919 if isinstance(then_expression.args.get("expressions"), exp.Star): 3920 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3921 else: 3922 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3923 else: 3924 then = self.sql(then_expression) 3925 return f"WHEN {matched}{source}{condition} THEN {then}" 3926 3927 def whens_sql(self, expression: exp.Whens) -> str: 3928 return self.expressions(expression, sep=" ", indent=False) 3929 3930 def merge_sql(self, expression: exp.Merge) -> str: 3931 table = expression.this 3932 table_alias = "" 3933 3934 hints = table.args.get("hints") 3935 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3936 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3937 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3938 3939 this = self.sql(table) 3940 using = f"USING {self.sql(expression, 'using')}" 3941 on = f"ON {self.sql(expression, 'on')}" 3942 whens = self.sql(expression, "whens") 3943 3944 returning = self.sql(expression, "returning") 3945 if returning: 3946 whens = f"{whens}{returning}" 3947 3948 sep = self.sep() 3949 3950 return self.prepend_ctes( 3951 expression, 3952 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3953 ) 3954 3955 @unsupported_args("format") 3956 def tochar_sql(self, expression: exp.ToChar) -> str: 3957 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3958 3959 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3960 if not self.SUPPORTS_TO_NUMBER: 3961 self.unsupported("Unsupported TO_NUMBER function") 3962 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3963 3964 fmt = expression.args.get("format") 3965 if not fmt: 3966 self.unsupported("Conversion format is required for TO_NUMBER") 3967 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3968 3969 return self.func("TO_NUMBER", expression.this, fmt) 3970 3971 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3972 this = self.sql(expression, "this") 3973 kind = self.sql(expression, "kind") 3974 settings_sql = self.expressions(expression, key="settings", sep=" ") 3975 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3976 return f"{this}({kind}{args})" 3977 3978 def dictrange_sql(self, expression: exp.DictRange) -> str: 3979 this = self.sql(expression, "this") 3980 max = self.sql(expression, "max") 3981 min = self.sql(expression, "min") 3982 return f"{this}(MIN {min} MAX {max})" 3983 3984 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3985 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3986 3987 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3988 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3989 3990 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3991 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3992 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3993 3994 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3995 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3996 expressions = self.expressions(expression, flat=True) 3997 expressions = f" {self.wrap(expressions)}" if expressions else "" 3998 buckets = self.sql(expression, "buckets") 3999 kind = self.sql(expression, "kind") 4000 buckets = f" BUCKETS {buckets}" if buckets else "" 4001 order = self.sql(expression, "order") 4002 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4003 4004 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4005 return "" 4006 4007 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4008 expressions = self.expressions(expression, key="expressions", flat=True) 4009 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4010 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4011 buckets = self.sql(expression, "buckets") 4012 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4013 4014 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4015 this = self.sql(expression, "this") 4016 having = self.sql(expression, "having") 4017 4018 if having: 4019 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4020 4021 return self.func("ANY_VALUE", this) 4022 4023 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4024 transform = self.func("TRANSFORM", *expression.expressions) 4025 row_format_before = self.sql(expression, "row_format_before") 4026 row_format_before = f" {row_format_before}" if row_format_before else "" 4027 record_writer = self.sql(expression, "record_writer") 4028 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4029 using = f" USING {self.sql(expression, 'command_script')}" 4030 schema = self.sql(expression, "schema") 4031 schema = f" AS {schema}" if schema else "" 4032 row_format_after = self.sql(expression, "row_format_after") 4033 row_format_after = f" {row_format_after}" if row_format_after else "" 4034 record_reader = self.sql(expression, "record_reader") 4035 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4036 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4037 4038 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4039 key_block_size = self.sql(expression, "key_block_size") 4040 if key_block_size: 4041 return f"KEY_BLOCK_SIZE = {key_block_size}" 4042 4043 using = self.sql(expression, "using") 4044 if using: 4045 return f"USING {using}" 4046 4047 parser = self.sql(expression, "parser") 4048 if parser: 4049 return f"WITH PARSER {parser}" 4050 4051 comment = self.sql(expression, "comment") 4052 if comment: 4053 return f"COMMENT {comment}" 4054 4055 visible = expression.args.get("visible") 4056 if visible is not None: 4057 return "VISIBLE" if visible else "INVISIBLE" 4058 4059 engine_attr = self.sql(expression, "engine_attr") 4060 if engine_attr: 4061 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4062 4063 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4064 if secondary_engine_attr: 4065 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4066 4067 self.unsupported("Unsupported index constraint option.") 4068 return "" 4069 4070 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4071 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4072 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4073 4074 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4075 kind = self.sql(expression, "kind") 4076 kind = f"{kind} INDEX" if kind else "INDEX" 4077 this = self.sql(expression, "this") 4078 this = f" {this}" if this else "" 4079 index_type = self.sql(expression, "index_type") 4080 index_type = f" USING {index_type}" if index_type else "" 4081 expressions = self.expressions(expression, flat=True) 4082 expressions = f" ({expressions})" if expressions else "" 4083 options = self.expressions(expression, key="options", sep=" ") 4084 options = f" {options}" if options else "" 4085 return f"{kind}{this}{index_type}{expressions}{options}" 4086 4087 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4088 if self.NVL2_SUPPORTED: 4089 return self.function_fallback_sql(expression) 4090 4091 case = exp.Case().when( 4092 expression.this.is_(exp.null()).not_(copy=False), 4093 expression.args["true"], 4094 copy=False, 4095 ) 4096 else_cond = expression.args.get("false") 4097 if else_cond: 4098 case.else_(else_cond, copy=False) 4099 4100 return self.sql(case) 4101 4102 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4103 this = self.sql(expression, "this") 4104 expr = self.sql(expression, "expression") 4105 iterator = self.sql(expression, "iterator") 4106 condition = self.sql(expression, "condition") 4107 condition = f" IF {condition}" if condition else "" 4108 return f"{this} FOR {expr} IN {iterator}{condition}" 4109 4110 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4111 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4112 4113 def opclass_sql(self, expression: exp.Opclass) -> str: 4114 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4115 4116 def predict_sql(self, expression: exp.Predict) -> str: 4117 model = self.sql(expression, "this") 4118 model = f"MODEL {model}" 4119 table = self.sql(expression, "expression") 4120 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4121 parameters = self.sql(expression, "params_struct") 4122 return self.func("PREDICT", model, table, parameters or None) 4123 4124 def forin_sql(self, expression: exp.ForIn) -> str: 4125 this = self.sql(expression, "this") 4126 expression_sql = self.sql(expression, "expression") 4127 return f"FOR {this} DO {expression_sql}" 4128 4129 def refresh_sql(self, expression: exp.Refresh) -> str: 4130 this = self.sql(expression, "this") 4131 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4132 return f"REFRESH {table}{this}" 4133 4134 def toarray_sql(self, expression: exp.ToArray) -> str: 4135 arg = expression.this 4136 if not arg.type: 4137 from sqlglot.optimizer.annotate_types import annotate_types 4138 4139 arg = annotate_types(arg, dialect=self.dialect) 4140 4141 if arg.is_type(exp.DataType.Type.ARRAY): 4142 return self.sql(arg) 4143 4144 cond_for_null = arg.is_(exp.null()) 4145 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4146 4147 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4148 this = expression.this 4149 time_format = self.format_time(expression) 4150 4151 if time_format: 4152 return self.sql( 4153 exp.cast( 4154 exp.StrToTime(this=this, format=expression.args["format"]), 4155 exp.DataType.Type.TIME, 4156 ) 4157 ) 4158 4159 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4160 return self.sql(this) 4161 4162 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4163 4164 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4165 this = expression.this 4166 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4167 return self.sql(this) 4168 4169 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4170 4171 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4172 this = expression.this 4173 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4174 return self.sql(this) 4175 4176 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4177 4178 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4179 this = expression.this 4180 time_format = self.format_time(expression) 4181 4182 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4183 return self.sql( 4184 exp.cast( 4185 exp.StrToTime(this=this, format=expression.args["format"]), 4186 exp.DataType.Type.DATE, 4187 ) 4188 ) 4189 4190 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4191 return self.sql(this) 4192 4193 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4194 4195 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4196 return self.sql( 4197 exp.func( 4198 "DATEDIFF", 4199 expression.this, 4200 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4201 "day", 4202 ) 4203 ) 4204 4205 def lastday_sql(self, expression: exp.LastDay) -> str: 4206 if self.LAST_DAY_SUPPORTS_DATE_PART: 4207 return self.function_fallback_sql(expression) 4208 4209 unit = expression.text("unit") 4210 if unit and unit != "MONTH": 4211 self.unsupported("Date parts are not supported in LAST_DAY.") 4212 4213 return self.func("LAST_DAY", expression.this) 4214 4215 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4216 from sqlglot.dialects.dialect import unit_to_str 4217 4218 return self.func( 4219 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4220 ) 4221 4222 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4223 if self.CAN_IMPLEMENT_ARRAY_ANY: 4224 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4225 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4226 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4227 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4228 4229 from sqlglot.dialects import Dialect 4230 4231 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4232 if self.dialect.__class__ != Dialect: 4233 self.unsupported("ARRAY_ANY is unsupported") 4234 4235 return self.function_fallback_sql(expression) 4236 4237 def struct_sql(self, expression: exp.Struct) -> str: 4238 expression.set( 4239 "expressions", 4240 [ 4241 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4242 if isinstance(e, exp.PropertyEQ) 4243 else e 4244 for e in expression.expressions 4245 ], 4246 ) 4247 4248 return self.function_fallback_sql(expression) 4249 4250 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4251 low = self.sql(expression, "this") 4252 high = self.sql(expression, "expression") 4253 4254 return f"{low} TO {high}" 4255 4256 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4257 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4258 tables = f" {self.expressions(expression)}" 4259 4260 exists = " IF EXISTS" if expression.args.get("exists") else "" 4261 4262 on_cluster = self.sql(expression, "cluster") 4263 on_cluster = f" {on_cluster}" if on_cluster else "" 4264 4265 identity = self.sql(expression, "identity") 4266 identity = f" {identity} IDENTITY" if identity else "" 4267 4268 option = self.sql(expression, "option") 4269 option = f" {option}" if option else "" 4270 4271 partition = self.sql(expression, "partition") 4272 partition = f" {partition}" if partition else "" 4273 4274 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4275 4276 # This transpiles T-SQL's CONVERT function 4277 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4278 def convert_sql(self, expression: exp.Convert) -> str: 4279 to = expression.this 4280 value = expression.expression 4281 style = expression.args.get("style") 4282 safe = expression.args.get("safe") 4283 strict = expression.args.get("strict") 4284 4285 if not to or not value: 4286 return "" 4287 4288 # Retrieve length of datatype and override to default if not specified 4289 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4290 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4291 4292 transformed: t.Optional[exp.Expression] = None 4293 cast = exp.Cast if strict else exp.TryCast 4294 4295 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4296 if isinstance(style, exp.Literal) and style.is_int: 4297 from sqlglot.dialects.tsql import TSQL 4298 4299 style_value = style.name 4300 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4301 if not converted_style: 4302 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4303 4304 fmt = exp.Literal.string(converted_style) 4305 4306 if to.this == exp.DataType.Type.DATE: 4307 transformed = exp.StrToDate(this=value, format=fmt) 4308 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4309 transformed = exp.StrToTime(this=value, format=fmt) 4310 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4311 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4312 elif to.this == exp.DataType.Type.TEXT: 4313 transformed = exp.TimeToStr(this=value, format=fmt) 4314 4315 if not transformed: 4316 transformed = cast(this=value, to=to, safe=safe) 4317 4318 return self.sql(transformed) 4319 4320 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4321 this = expression.this 4322 if isinstance(this, exp.JSONPathWildcard): 4323 this = self.json_path_part(this) 4324 return f".{this}" if this else "" 4325 4326 if exp.SAFE_IDENTIFIER_RE.match(this): 4327 return f".{this}" 4328 4329 this = self.json_path_part(this) 4330 return ( 4331 f"[{this}]" 4332 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4333 else f".{this}" 4334 ) 4335 4336 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4337 this = self.json_path_part(expression.this) 4338 return f"[{this}]" if this else "" 4339 4340 def _simplify_unless_literal(self, expression: E) -> E: 4341 if not isinstance(expression, exp.Literal): 4342 from sqlglot.optimizer.simplify import simplify 4343 4344 expression = simplify(expression, dialect=self.dialect) 4345 4346 return expression 4347 4348 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4349 this = expression.this 4350 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4351 self.unsupported( 4352 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4353 ) 4354 return self.sql(this) 4355 4356 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4357 # The first modifier here will be the one closest to the AggFunc's arg 4358 mods = sorted( 4359 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4360 key=lambda x: 0 4361 if isinstance(x, exp.HavingMax) 4362 else (1 if isinstance(x, exp.Order) else 2), 4363 ) 4364 4365 if mods: 4366 mod = mods[0] 4367 this = expression.__class__(this=mod.this.copy()) 4368 this.meta["inline"] = True 4369 mod.this.replace(this) 4370 return self.sql(expression.this) 4371 4372 agg_func = expression.find(exp.AggFunc) 4373 4374 if agg_func: 4375 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4376 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4377 4378 return f"{self.sql(expression, 'this')} {text}" 4379 4380 def _replace_line_breaks(self, string: str) -> str: 4381 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4382 if self.pretty: 4383 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4384 return string 4385 4386 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4387 option = self.sql(expression, "this") 4388 4389 if expression.expressions: 4390 upper = option.upper() 4391 4392 # Snowflake FILE_FORMAT options are separated by whitespace 4393 sep = " " if upper == "FILE_FORMAT" else ", " 4394 4395 # Databricks copy/format options do not set their list of values with EQ 4396 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4397 values = self.expressions(expression, flat=True, sep=sep) 4398 return f"{option}{op}({values})" 4399 4400 value = self.sql(expression, "expression") 4401 4402 if not value: 4403 return option 4404 4405 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4406 4407 return f"{option}{op}{value}" 4408 4409 def credentials_sql(self, expression: exp.Credentials) -> str: 4410 cred_expr = expression.args.get("credentials") 4411 if isinstance(cred_expr, exp.Literal): 4412 # Redshift case: CREDENTIALS <string> 4413 credentials = self.sql(expression, "credentials") 4414 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4415 else: 4416 # Snowflake case: CREDENTIALS = (...) 4417 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4418 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4419 4420 storage = self.sql(expression, "storage") 4421 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4422 4423 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4424 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4425 4426 iam_role = self.sql(expression, "iam_role") 4427 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4428 4429 region = self.sql(expression, "region") 4430 region = f" REGION {region}" if region else "" 4431 4432 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4433 4434 def copy_sql(self, expression: exp.Copy) -> str: 4435 this = self.sql(expression, "this") 4436 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4437 4438 credentials = self.sql(expression, "credentials") 4439 credentials = self.seg(credentials) if credentials else "" 4440 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4441 files = self.expressions(expression, key="files", flat=True) 4442 4443 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4444 params = self.expressions( 4445 expression, 4446 key="params", 4447 sep=sep, 4448 new_line=True, 4449 skip_last=True, 4450 skip_first=True, 4451 indent=self.COPY_PARAMS_ARE_WRAPPED, 4452 ) 4453 4454 if params: 4455 if self.COPY_PARAMS_ARE_WRAPPED: 4456 params = f" WITH ({params})" 4457 elif not self.pretty: 4458 params = f" {params}" 4459 4460 return f"COPY{this}{kind} {files}{credentials}{params}" 4461 4462 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4463 return "" 4464 4465 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4466 on_sql = "ON" if expression.args.get("on") else "OFF" 4467 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4468 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4469 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4470 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4471 4472 if filter_col or retention_period: 4473 on_sql = self.func("ON", filter_col, retention_period) 4474 4475 return f"DATA_DELETION={on_sql}" 4476 4477 def maskingpolicycolumnconstraint_sql( 4478 self, expression: exp.MaskingPolicyColumnConstraint 4479 ) -> str: 4480 this = self.sql(expression, "this") 4481 expressions = self.expressions(expression, flat=True) 4482 expressions = f" USING ({expressions})" if expressions else "" 4483 return f"MASKING POLICY {this}{expressions}" 4484 4485 def gapfill_sql(self, expression: exp.GapFill) -> str: 4486 this = self.sql(expression, "this") 4487 this = f"TABLE {this}" 4488 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4489 4490 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4491 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4492 4493 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4494 this = self.sql(expression, "this") 4495 expr = expression.expression 4496 4497 if isinstance(expr, exp.Func): 4498 # T-SQL's CLR functions are case sensitive 4499 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4500 else: 4501 expr = self.sql(expression, "expression") 4502 4503 return self.scope_resolution(expr, this) 4504 4505 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4506 if self.PARSE_JSON_NAME is None: 4507 return self.sql(expression.this) 4508 4509 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4510 4511 def rand_sql(self, expression: exp.Rand) -> str: 4512 lower = self.sql(expression, "lower") 4513 upper = self.sql(expression, "upper") 4514 4515 if lower and upper: 4516 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4517 return self.func("RAND", expression.this) 4518 4519 def changes_sql(self, expression: exp.Changes) -> str: 4520 information = self.sql(expression, "information") 4521 information = f"INFORMATION => {information}" 4522 at_before = self.sql(expression, "at_before") 4523 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4524 end = self.sql(expression, "end") 4525 end = f"{self.seg('')}{end}" if end else "" 4526 4527 return f"CHANGES ({information}){at_before}{end}" 4528 4529 def pad_sql(self, expression: exp.Pad) -> str: 4530 prefix = "L" if expression.args.get("is_left") else "R" 4531 4532 fill_pattern = self.sql(expression, "fill_pattern") or None 4533 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4534 fill_pattern = "' '" 4535 4536 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4537 4538 def summarize_sql(self, expression: exp.Summarize) -> str: 4539 table = " TABLE" if expression.args.get("table") else "" 4540 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4541 4542 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4543 generate_series = exp.GenerateSeries(**expression.args) 4544 4545 parent = expression.parent 4546 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4547 parent = parent.parent 4548 4549 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4550 return self.sql(exp.Unnest(expressions=[generate_series])) 4551 4552 if isinstance(parent, exp.Select): 4553 self.unsupported("GenerateSeries projection unnesting is not supported.") 4554 4555 return self.sql(generate_series) 4556 4557 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4558 exprs = expression.expressions 4559 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4560 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4561 else: 4562 rhs = self.expressions(expression) 4563 4564 return self.func(name, expression.this, rhs or None) 4565 4566 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4567 if self.SUPPORTS_CONVERT_TIMEZONE: 4568 return self.function_fallback_sql(expression) 4569 4570 source_tz = expression.args.get("source_tz") 4571 target_tz = expression.args.get("target_tz") 4572 timestamp = expression.args.get("timestamp") 4573 4574 if source_tz and timestamp: 4575 timestamp = exp.AtTimeZone( 4576 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4577 ) 4578 4579 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4580 4581 return self.sql(expr) 4582 4583 def json_sql(self, expression: exp.JSON) -> str: 4584 this = self.sql(expression, "this") 4585 this = f" {this}" if this else "" 4586 4587 _with = expression.args.get("with") 4588 4589 if _with is None: 4590 with_sql = "" 4591 elif not _with: 4592 with_sql = " WITHOUT" 4593 else: 4594 with_sql = " WITH" 4595 4596 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4597 4598 return f"JSON{this}{with_sql}{unique_sql}" 4599 4600 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4601 def _generate_on_options(arg: t.Any) -> str: 4602 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4603 4604 path = self.sql(expression, "path") 4605 returning = self.sql(expression, "returning") 4606 returning = f" RETURNING {returning}" if returning else "" 4607 4608 on_condition = self.sql(expression, "on_condition") 4609 on_condition = f" {on_condition}" if on_condition else "" 4610 4611 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4612 4613 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4614 else_ = "ELSE " if expression.args.get("else_") else "" 4615 condition = self.sql(expression, "expression") 4616 condition = f"WHEN {condition} THEN " if condition else else_ 4617 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4618 return f"{condition}{insert}" 4619 4620 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4621 kind = self.sql(expression, "kind") 4622 expressions = self.seg(self.expressions(expression, sep=" ")) 4623 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4624 return res 4625 4626 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4627 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4628 empty = expression.args.get("empty") 4629 empty = ( 4630 f"DEFAULT {empty} ON EMPTY" 4631 if isinstance(empty, exp.Expression) 4632 else self.sql(expression, "empty") 4633 ) 4634 4635 error = expression.args.get("error") 4636 error = ( 4637 f"DEFAULT {error} ON ERROR" 4638 if isinstance(error, exp.Expression) 4639 else self.sql(expression, "error") 4640 ) 4641 4642 if error and empty: 4643 error = ( 4644 f"{empty} {error}" 4645 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4646 else f"{error} {empty}" 4647 ) 4648 empty = "" 4649 4650 null = self.sql(expression, "null") 4651 4652 return f"{empty}{error}{null}" 4653 4654 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4655 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4656 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4657 4658 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4659 this = self.sql(expression, "this") 4660 path = self.sql(expression, "path") 4661 4662 passing = self.expressions(expression, "passing") 4663 passing = f" PASSING {passing}" if passing else "" 4664 4665 on_condition = self.sql(expression, "on_condition") 4666 on_condition = f" {on_condition}" if on_condition else "" 4667 4668 path = f"{path}{passing}{on_condition}" 4669 4670 return self.func("JSON_EXISTS", this, path) 4671 4672 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4673 array_agg = self.function_fallback_sql(expression) 4674 4675 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4676 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4677 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4678 parent = expression.parent 4679 if isinstance(parent, exp.Filter): 4680 parent_cond = parent.expression.this 4681 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4682 else: 4683 this = expression.this 4684 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4685 if this.find(exp.Column): 4686 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4687 this_sql = ( 4688 self.expressions(this) 4689 if isinstance(this, exp.Distinct) 4690 else self.sql(expression, "this") 4691 ) 4692 4693 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4694 4695 return array_agg 4696 4697 def apply_sql(self, expression: exp.Apply) -> str: 4698 this = self.sql(expression, "this") 4699 expr = self.sql(expression, "expression") 4700 4701 return f"{this} APPLY({expr})" 4702 4703 def grant_sql(self, expression: exp.Grant) -> str: 4704 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4705 4706 kind = self.sql(expression, "kind") 4707 kind = f" {kind}" if kind else "" 4708 4709 securable = self.sql(expression, "securable") 4710 securable = f" {securable}" if securable else "" 4711 4712 principals = self.expressions(expression, key="principals", flat=True) 4713 4714 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4715 4716 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4717 4718 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4719 this = self.sql(expression, "this") 4720 columns = self.expressions(expression, flat=True) 4721 columns = f"({columns})" if columns else "" 4722 4723 return f"{this}{columns}" 4724 4725 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4726 this = self.sql(expression, "this") 4727 4728 kind = self.sql(expression, "kind") 4729 kind = f"{kind} " if kind else "" 4730 4731 return f"{kind}{this}" 4732 4733 def columns_sql(self, expression: exp.Columns): 4734 func = self.function_fallback_sql(expression) 4735 if expression.args.get("unpack"): 4736 func = f"*{func}" 4737 4738 return func 4739 4740 def overlay_sql(self, expression: exp.Overlay): 4741 this = self.sql(expression, "this") 4742 expr = self.sql(expression, "expression") 4743 from_sql = self.sql(expression, "from") 4744 for_sql = self.sql(expression, "for") 4745 for_sql = f" FOR {for_sql}" if for_sql else "" 4746 4747 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4748 4749 @unsupported_args("format") 4750 def todouble_sql(self, expression: exp.ToDouble) -> str: 4751 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4752 4753 def string_sql(self, expression: exp.String) -> str: 4754 this = expression.this 4755 zone = expression.args.get("zone") 4756 4757 if zone: 4758 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4759 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4760 # set for source_tz to transpile the time conversion before the STRING cast 4761 this = exp.ConvertTimezone( 4762 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4763 ) 4764 4765 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4766 4767 def median_sql(self, expression: exp.Median): 4768 if not self.SUPPORTS_MEDIAN: 4769 return self.sql( 4770 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4771 ) 4772 4773 return self.function_fallback_sql(expression) 4774 4775 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4776 filler = self.sql(expression, "this") 4777 filler = f" {filler}" if filler else "" 4778 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4779 return f"TRUNCATE{filler} {with_count}" 4780 4781 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4782 if self.SUPPORTS_UNIX_SECONDS: 4783 return self.function_fallback_sql(expression) 4784 4785 start_ts = exp.cast( 4786 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4787 ) 4788 4789 return self.sql( 4790 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4791 ) 4792 4793 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4794 dim = expression.expression 4795 4796 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4797 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4798 if not (dim.is_int and dim.name == "1"): 4799 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4800 dim = None 4801 4802 # If dimension is required but not specified, default initialize it 4803 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4804 dim = exp.Literal.number(1) 4805 4806 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4807 4808 def attach_sql(self, expression: exp.Attach) -> str: 4809 this = self.sql(expression, "this") 4810 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4811 expressions = self.expressions(expression) 4812 expressions = f" ({expressions})" if expressions else "" 4813 4814 return f"ATTACH{exists_sql} {this}{expressions}" 4815 4816 def detach_sql(self, expression: exp.Detach) -> str: 4817 this = self.sql(expression, "this") 4818 # the DATABASE keyword is required if IF EXISTS is set 4819 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4820 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4821 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4822 4823 return f"DETACH{exists_sql} {this}" 4824 4825 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4826 this = self.sql(expression, "this") 4827 value = self.sql(expression, "expression") 4828 value = f" {value}" if value else "" 4829 return f"{this}{value}" 4830 4831 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4832 this_sql = self.sql(expression, "this") 4833 if isinstance(expression.this, exp.Table): 4834 this_sql = f"TABLE {this_sql}" 4835 4836 return self.func( 4837 "FEATURES_AT_TIME", 4838 this_sql, 4839 expression.args.get("time"), 4840 expression.args.get("num_rows"), 4841 expression.args.get("ignore_feature_nulls"), 4842 ) 4843 4844 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4845 return ( 4846 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4847 ) 4848 4849 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4850 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4851 encode = f"{encode} {self.sql(expression, 'this')}" 4852 4853 properties = expression.args.get("properties") 4854 if properties: 4855 encode = f"{encode} {self.properties(properties)}" 4856 4857 return encode 4858 4859 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4860 this = self.sql(expression, "this") 4861 include = f"INCLUDE {this}" 4862 4863 column_def = self.sql(expression, "column_def") 4864 if column_def: 4865 include = f"{include} {column_def}" 4866 4867 alias = self.sql(expression, "alias") 4868 if alias: 4869 include = f"{include} AS {alias}" 4870 4871 return include 4872 4873 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4874 name = f"NAME {self.sql(expression, 'this')}" 4875 return self.func("XMLELEMENT", name, *expression.expressions) 4876 4877 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4878 this = self.sql(expression, "this") 4879 expr = self.sql(expression, "expression") 4880 expr = f"({expr})" if expr else "" 4881 return f"{this}{expr}" 4882 4883 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4884 partitions = self.expressions(expression, "partition_expressions") 4885 create = self.expressions(expression, "create_expressions") 4886 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4887 4888 def partitionbyrangepropertydynamic_sql( 4889 self, expression: exp.PartitionByRangePropertyDynamic 4890 ) -> str: 4891 start = self.sql(expression, "start") 4892 end = self.sql(expression, "end") 4893 4894 every = expression.args["every"] 4895 if isinstance(every, exp.Interval) and every.this.is_string: 4896 every.this.replace(exp.Literal.number(every.name)) 4897 4898 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4899 4900 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4901 name = self.sql(expression, "this") 4902 values = self.expressions(expression, flat=True) 4903 4904 return f"NAME {name} VALUE {values}" 4905 4906 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4907 kind = self.sql(expression, "kind") 4908 sample = self.sql(expression, "sample") 4909 return f"SAMPLE {sample} {kind}" 4910 4911 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4912 kind = self.sql(expression, "kind") 4913 option = self.sql(expression, "option") 4914 option = f" {option}" if option else "" 4915 this = self.sql(expression, "this") 4916 this = f" {this}" if this else "" 4917 columns = self.expressions(expression) 4918 columns = f" {columns}" if columns else "" 4919 return f"{kind}{option} STATISTICS{this}{columns}" 4920 4921 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4922 this = self.sql(expression, "this") 4923 columns = self.expressions(expression) 4924 inner_expression = self.sql(expression, "expression") 4925 inner_expression = f" {inner_expression}" if inner_expression else "" 4926 update_options = self.sql(expression, "update_options") 4927 update_options = f" {update_options} UPDATE" if update_options else "" 4928 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4929 4930 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4931 kind = self.sql(expression, "kind") 4932 kind = f" {kind}" if kind else "" 4933 return f"DELETE{kind} STATISTICS" 4934 4935 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4936 inner_expression = self.sql(expression, "expression") 4937 return f"LIST CHAINED ROWS{inner_expression}" 4938 4939 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4940 kind = self.sql(expression, "kind") 4941 this = self.sql(expression, "this") 4942 this = f" {this}" if this else "" 4943 inner_expression = self.sql(expression, "expression") 4944 return f"VALIDATE {kind}{this}{inner_expression}" 4945 4946 def analyze_sql(self, expression: exp.Analyze) -> str: 4947 options = self.expressions(expression, key="options", sep=" ") 4948 options = f" {options}" if options else "" 4949 kind = self.sql(expression, "kind") 4950 kind = f" {kind}" if kind else "" 4951 this = self.sql(expression, "this") 4952 this = f" {this}" if this else "" 4953 mode = self.sql(expression, "mode") 4954 mode = f" {mode}" if mode else "" 4955 properties = self.sql(expression, "properties") 4956 properties = f" {properties}" if properties else "" 4957 partition = self.sql(expression, "partition") 4958 partition = f" {partition}" if partition else "" 4959 inner_expression = self.sql(expression, "expression") 4960 inner_expression = f" {inner_expression}" if inner_expression else "" 4961 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4962 4963 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4964 this = self.sql(expression, "this") 4965 namespaces = self.expressions(expression, key="namespaces") 4966 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4967 passing = self.expressions(expression, key="passing") 4968 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4969 columns = self.expressions(expression, key="columns") 4970 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4971 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4972 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4973 4974 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4975 this = self.sql(expression, "this") 4976 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4977 4978 def export_sql(self, expression: exp.Export) -> str: 4979 this = self.sql(expression, "this") 4980 connection = self.sql(expression, "connection") 4981 connection = f"WITH CONNECTION {connection} " if connection else "" 4982 options = self.sql(expression, "options") 4983 return f"EXPORT DATA {connection}{options} AS {this}" 4984 4985 def declare_sql(self, expression: exp.Declare) -> str: 4986 return f"DECLARE {self.expressions(expression, flat=True)}" 4987 4988 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4989 variable = self.sql(expression, "this") 4990 default = self.sql(expression, "default") 4991 default = f" = {default}" if default else "" 4992 4993 kind = self.sql(expression, "kind") 4994 if isinstance(expression.args.get("kind"), exp.Schema): 4995 kind = f"TABLE {kind}" 4996 4997 return f"{variable} AS {kind}{default}" 4998 4999 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5000 kind = self.sql(expression, "kind") 5001 this = self.sql(expression, "this") 5002 set = self.sql(expression, "expression") 5003 using = self.sql(expression, "using") 5004 using = f" USING {using}" if using else "" 5005 5006 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5007 5008 return f"{kind_sql} {this} SET {set}{using}" 5009 5010 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5011 params = self.expressions(expression, key="params", flat=True) 5012 return self.func(expression.name, *expression.expressions) + f"({params})" 5013 5014 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5015 return self.func(expression.name, *expression.expressions) 5016 5017 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5018 return self.anonymousaggfunc_sql(expression) 5019 5020 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5021 return self.parameterizedagg_sql(expression) 5022 5023 def show_sql(self, expression: exp.Show) -> str: 5024 self.unsupported("Unsupported SHOW statement") 5025 return "" 5026 5027 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5028 # Snowflake GET/PUT statements: 5029 # PUT <file> <internalStage> <properties> 5030 # GET <internalStage> <file> <properties> 5031 props = expression.args.get("properties") 5032 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5033 this = self.sql(expression, "this") 5034 target = self.sql(expression, "target") 5035 5036 if isinstance(expression, exp.Put): 5037 return f"PUT {this} {target}{props_sql}" 5038 else: 5039 return f"GET {target} {this}{props_sql}" 5040 5041 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5042 this = self.sql(expression, "this") 5043 expr = self.sql(expression, "expression") 5044 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5045 return f"TRANSLATE({this} USING {expr}{with_error})" 5046 5047 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5048 if self.SUPPORTS_DECODE_CASE: 5049 return self.func("DECODE", *expression.expressions) 5050 5051 expression, *expressions = expression.expressions 5052 5053 ifs = [] 5054 for search, result in zip(expressions[::2], expressions[1::2]): 5055 if isinstance(search, exp.Literal): 5056 ifs.append(exp.If(this=expression.eq(search), true=result)) 5057 elif isinstance(search, exp.Null): 5058 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5059 else: 5060 if isinstance(search, exp.Binary): 5061 search = exp.paren(search) 5062 5063 cond = exp.or_( 5064 expression.eq(search), 5065 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5066 copy=False, 5067 ) 5068 ifs.append(exp.If(this=cond, true=result)) 5069 5070 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5071 return self.sql(case) 5072 5073 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5074 this = self.sql(expression, "this") 5075 this = self.seg(this, sep="") 5076 dimensions = self.expressions( 5077 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5078 ) 5079 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5080 metrics = self.expressions( 5081 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5082 ) 5083 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5084 where = self.sql(expression, "where") 5085 where = self.seg(f"WHERE {where}") if where else "" 5086 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.LanguageProperty: lambda self, e: self.naked_property(e), 165 exp.LocationProperty: lambda self, e: self.naked_property(e), 166 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 167 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 168 exp.NonClusteredColumnConstraint: lambda self, 169 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 170 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 171 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 172 exp.OnCommitProperty: lambda _, 173 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 174 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 175 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 176 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 177 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 178 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 179 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 180 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 181 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 182 exp.PositionalColumn: lambda self, e: f"#{self.sql(e, 'this')}", 183 exp.ProjectionPolicyColumnConstraint: lambda self, 184 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 185 exp.Put: lambda self, e: self.get_put_sql(e), 186 exp.RemoteWithConnectionModelProperty: lambda self, 187 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 188 exp.ReturnsProperty: lambda self, e: ( 189 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 190 ), 191 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 192 exp.SecureProperty: lambda *_: "SECURE", 193 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 194 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 195 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 196 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 197 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 198 exp.SqlReadWriteProperty: lambda _, e: e.name, 199 exp.SqlSecurityProperty: lambda _, 200 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 201 exp.StabilityProperty: lambda _, e: e.name, 202 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 203 exp.StreamingTableProperty: lambda *_: "STREAMING", 204 exp.StrictProperty: lambda *_: "STRICT", 205 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 206 exp.TableColumn: lambda self, e: self.sql(e.this), 207 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 208 exp.TemporaryProperty: lambda *_: "TEMPORARY", 209 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 210 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 211 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 212 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 213 exp.TransientProperty: lambda *_: "TRANSIENT", 214 exp.Union: lambda self, e: self.set_operations(e), 215 exp.UnloggedProperty: lambda *_: "UNLOGGED", 216 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 217 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 218 exp.Uuid: lambda *_: "UUID()", 219 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 220 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 221 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 222 exp.VolatileProperty: lambda *_: "VOLATILE", 223 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 224 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 225 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 226 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 227 exp.ForceProperty: lambda *_: "FORCE", 228 } 229 230 # Whether null ordering is supported in order by 231 # True: Full Support, None: No support, False: No support for certain cases 232 # such as window specifications, aggregate functions etc 233 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 234 235 # Whether ignore nulls is inside the agg or outside. 236 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 237 IGNORE_NULLS_IN_FUNC = False 238 239 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 240 LOCKING_READS_SUPPORTED = False 241 242 # Whether the EXCEPT and INTERSECT operations can return duplicates 243 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 244 245 # Wrap derived values in parens, usually standard but spark doesn't support it 246 WRAP_DERIVED_VALUES = True 247 248 # Whether create function uses an AS before the RETURN 249 CREATE_FUNCTION_RETURN_AS = True 250 251 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 252 MATCHED_BY_SOURCE = True 253 254 # Whether the INTERVAL expression works only with values like '1 day' 255 SINGLE_STRING_INTERVAL = False 256 257 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 258 INTERVAL_ALLOWS_PLURAL_FORM = True 259 260 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 261 LIMIT_FETCH = "ALL" 262 263 # Whether limit and fetch allows expresions or just limits 264 LIMIT_ONLY_LITERALS = False 265 266 # Whether a table is allowed to be renamed with a db 267 RENAME_TABLE_WITH_DB = True 268 269 # The separator for grouping sets and rollups 270 GROUPINGS_SEP = "," 271 272 # The string used for creating an index on a table 273 INDEX_ON = "ON" 274 275 # Whether join hints should be generated 276 JOIN_HINTS = True 277 278 # Whether table hints should be generated 279 TABLE_HINTS = True 280 281 # Whether query hints should be generated 282 QUERY_HINTS = True 283 284 # What kind of separator to use for query hints 285 QUERY_HINT_SEP = ", " 286 287 # Whether comparing against booleans (e.g. x IS TRUE) is supported 288 IS_BOOL_ALLOWED = True 289 290 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 291 DUPLICATE_KEY_UPDATE_WITH_SET = True 292 293 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 294 LIMIT_IS_TOP = False 295 296 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 297 RETURNING_END = True 298 299 # Whether to generate an unquoted value for EXTRACT's date part argument 300 EXTRACT_ALLOWS_QUOTES = True 301 302 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 303 TZ_TO_WITH_TIME_ZONE = False 304 305 # Whether the NVL2 function is supported 306 NVL2_SUPPORTED = True 307 308 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 309 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 310 311 # Whether VALUES statements can be used as derived tables. 312 # MySQL 5 and Redshift do not allow this, so when False, it will convert 313 # SELECT * VALUES into SELECT UNION 314 VALUES_AS_TABLE = True 315 316 # Whether the word COLUMN is included when adding a column with ALTER TABLE 317 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 318 319 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 320 UNNEST_WITH_ORDINALITY = True 321 322 # Whether FILTER (WHERE cond) can be used for conditional aggregation 323 AGGREGATE_FILTER_SUPPORTED = True 324 325 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 326 SEMI_ANTI_JOIN_WITH_SIDE = True 327 328 # Whether to include the type of a computed column in the CREATE DDL 329 COMPUTED_COLUMN_WITH_TYPE = True 330 331 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 332 SUPPORTS_TABLE_COPY = True 333 334 # Whether parentheses are required around the table sample's expression 335 TABLESAMPLE_REQUIRES_PARENS = True 336 337 # Whether a table sample clause's size needs to be followed by the ROWS keyword 338 TABLESAMPLE_SIZE_IS_ROWS = True 339 340 # The keyword(s) to use when generating a sample clause 341 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 342 343 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 344 TABLESAMPLE_WITH_METHOD = True 345 346 # The keyword to use when specifying the seed of a sample clause 347 TABLESAMPLE_SEED_KEYWORD = "SEED" 348 349 # Whether COLLATE is a function instead of a binary operator 350 COLLATE_IS_FUNC = False 351 352 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 353 DATA_TYPE_SPECIFIERS_ALLOWED = False 354 355 # Whether conditions require booleans WHERE x = 0 vs WHERE x 356 ENSURE_BOOLS = False 357 358 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 359 CTE_RECURSIVE_KEYWORD_REQUIRED = True 360 361 # Whether CONCAT requires >1 arguments 362 SUPPORTS_SINGLE_ARG_CONCAT = True 363 364 # Whether LAST_DAY function supports a date part argument 365 LAST_DAY_SUPPORTS_DATE_PART = True 366 367 # Whether named columns are allowed in table aliases 368 SUPPORTS_TABLE_ALIAS_COLUMNS = True 369 370 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 371 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 372 373 # What delimiter to use for separating JSON key/value pairs 374 JSON_KEY_VALUE_PAIR_SEP = ":" 375 376 # INSERT OVERWRITE TABLE x override 377 INSERT_OVERWRITE = " OVERWRITE TABLE" 378 379 # Whether the SELECT .. INTO syntax is used instead of CTAS 380 SUPPORTS_SELECT_INTO = False 381 382 # Whether UNLOGGED tables can be created 383 SUPPORTS_UNLOGGED_TABLES = False 384 385 # Whether the CREATE TABLE LIKE statement is supported 386 SUPPORTS_CREATE_TABLE_LIKE = True 387 388 # Whether the LikeProperty needs to be specified inside of the schema clause 389 LIKE_PROPERTY_INSIDE_SCHEMA = False 390 391 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 392 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 393 MULTI_ARG_DISTINCT = True 394 395 # Whether the JSON extraction operators expect a value of type JSON 396 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 397 398 # Whether bracketed keys like ["foo"] are supported in JSON paths 399 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 400 401 # Whether to escape keys using single quotes in JSON paths 402 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 403 404 # The JSONPathPart expressions supported by this dialect 405 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 406 407 # Whether any(f(x) for x in array) can be implemented by this dialect 408 CAN_IMPLEMENT_ARRAY_ANY = False 409 410 # Whether the function TO_NUMBER is supported 411 SUPPORTS_TO_NUMBER = True 412 413 # Whether EXCLUDE in window specification is supported 414 SUPPORTS_WINDOW_EXCLUDE = False 415 416 # Whether or not set op modifiers apply to the outer set op or select. 417 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 418 # True means limit 1 happens after the set op, False means it it happens on y. 419 SET_OP_MODIFIERS = True 420 421 # Whether parameters from COPY statement are wrapped in parentheses 422 COPY_PARAMS_ARE_WRAPPED = True 423 424 # Whether values of params are set with "=" token or empty space 425 COPY_PARAMS_EQ_REQUIRED = False 426 427 # Whether COPY statement has INTO keyword 428 COPY_HAS_INTO_KEYWORD = True 429 430 # Whether the conditional TRY(expression) function is supported 431 TRY_SUPPORTED = True 432 433 # Whether the UESCAPE syntax in unicode strings is supported 434 SUPPORTS_UESCAPE = True 435 436 # The keyword to use when generating a star projection with excluded columns 437 STAR_EXCEPT = "EXCEPT" 438 439 # The HEX function name 440 HEX_FUNC = "HEX" 441 442 # The keywords to use when prefixing & separating WITH based properties 443 WITH_PROPERTIES_PREFIX = "WITH" 444 445 # Whether to quote the generated expression of exp.JsonPath 446 QUOTE_JSON_PATH = True 447 448 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 449 PAD_FILL_PATTERN_IS_REQUIRED = False 450 451 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 452 SUPPORTS_EXPLODING_PROJECTIONS = True 453 454 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 455 ARRAY_CONCAT_IS_VAR_LEN = True 456 457 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 458 SUPPORTS_CONVERT_TIMEZONE = False 459 460 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 461 SUPPORTS_MEDIAN = True 462 463 # Whether UNIX_SECONDS(timestamp) is supported 464 SUPPORTS_UNIX_SECONDS = False 465 466 # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>) 467 ALTER_SET_WRAPPED = False 468 469 # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation 470 # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect. 471 # TODO: The normalization should be done by default once we've tested it across all dialects. 472 NORMALIZE_EXTRACT_DATE_PARTS = False 473 474 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 475 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 476 477 # The function name of the exp.ArraySize expression 478 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 479 480 # The syntax to use when altering the type of a column 481 ALTER_SET_TYPE = "SET DATA TYPE" 482 483 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 484 # None -> Doesn't support it at all 485 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 486 # True (Postgres) -> Explicitly requires it 487 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 488 489 # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated 490 SUPPORTS_DECODE_CASE = True 491 492 # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression 493 SUPPORTS_BETWEEN_FLAGS = False 494 495 TYPE_MAPPING = { 496 exp.DataType.Type.DATETIME2: "TIMESTAMP", 497 exp.DataType.Type.NCHAR: "CHAR", 498 exp.DataType.Type.NVARCHAR: "VARCHAR", 499 exp.DataType.Type.MEDIUMTEXT: "TEXT", 500 exp.DataType.Type.LONGTEXT: "TEXT", 501 exp.DataType.Type.TINYTEXT: "TEXT", 502 exp.DataType.Type.BLOB: "VARBINARY", 503 exp.DataType.Type.MEDIUMBLOB: "BLOB", 504 exp.DataType.Type.LONGBLOB: "BLOB", 505 exp.DataType.Type.TINYBLOB: "BLOB", 506 exp.DataType.Type.INET: "INET", 507 exp.DataType.Type.ROWVERSION: "VARBINARY", 508 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 509 } 510 511 TIME_PART_SINGULARS = { 512 "MICROSECONDS": "MICROSECOND", 513 "SECONDS": "SECOND", 514 "MINUTES": "MINUTE", 515 "HOURS": "HOUR", 516 "DAYS": "DAY", 517 "WEEKS": "WEEK", 518 "MONTHS": "MONTH", 519 "QUARTERS": "QUARTER", 520 "YEARS": "YEAR", 521 } 522 523 AFTER_HAVING_MODIFIER_TRANSFORMS = { 524 "cluster": lambda self, e: self.sql(e, "cluster"), 525 "distribute": lambda self, e: self.sql(e, "distribute"), 526 "sort": lambda self, e: self.sql(e, "sort"), 527 "windows": lambda self, e: ( 528 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 529 if e.args.get("windows") 530 else "" 531 ), 532 "qualify": lambda self, e: self.sql(e, "qualify"), 533 } 534 535 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 536 537 STRUCT_DELIMITER = ("<", ">") 538 539 PARAMETER_TOKEN = "@" 540 NAMED_PLACEHOLDER_TOKEN = ":" 541 542 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 543 544 PROPERTIES_LOCATION = { 545 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 546 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 547 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 551 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 553 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 556 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 557 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 558 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 559 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 560 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 561 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 562 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 563 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 565 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 567 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 569 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 573 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 574 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 575 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 576 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 577 exp.HeapProperty: exp.Properties.Location.POST_WITH, 578 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 579 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 580 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 583 exp.JournalProperty: exp.Properties.Location.POST_NAME, 584 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 589 exp.LogProperty: exp.Properties.Location.POST_NAME, 590 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 591 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 592 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 593 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 595 exp.Order: exp.Properties.Location.POST_SCHEMA, 596 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 598 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 600 exp.Property: exp.Properties.Location.POST_WITH, 601 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 609 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 611 exp.Set: exp.Properties.Location.POST_SCHEMA, 612 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.SetProperty: exp.Properties.Location.POST_CREATE, 614 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 616 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 617 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 618 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 619 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 620 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 623 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 624 exp.Tags: exp.Properties.Location.POST_WITH, 625 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 626 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 627 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 628 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 629 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 630 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 631 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 632 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 633 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 634 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 635 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 636 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 637 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 638 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 639 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 640 } 641 642 # Keywords that can't be used as unquoted identifier names 643 RESERVED_KEYWORDS: t.Set[str] = set() 644 645 # Expressions whose comments are separated from them for better formatting 646 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 647 exp.Command, 648 exp.Create, 649 exp.Describe, 650 exp.Delete, 651 exp.Drop, 652 exp.From, 653 exp.Insert, 654 exp.Join, 655 exp.MultitableInserts, 656 exp.Order, 657 exp.Group, 658 exp.Having, 659 exp.Select, 660 exp.SetOperation, 661 exp.Update, 662 exp.Where, 663 exp.With, 664 ) 665 666 # Expressions that should not have their comments generated in maybe_comment 667 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 668 exp.Binary, 669 exp.SetOperation, 670 ) 671 672 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 673 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 674 exp.Column, 675 exp.Literal, 676 exp.Neg, 677 exp.Paren, 678 ) 679 680 PARAMETERIZABLE_TEXT_TYPES = { 681 exp.DataType.Type.NVARCHAR, 682 exp.DataType.Type.VARCHAR, 683 exp.DataType.Type.CHAR, 684 exp.DataType.Type.NCHAR, 685 } 686 687 # Expressions that need to have all CTEs under them bubbled up to them 688 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 689 690 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 691 692 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 693 694 __slots__ = ( 695 "pretty", 696 "identify", 697 "normalize", 698 "pad", 699 "_indent", 700 "normalize_functions", 701 "unsupported_level", 702 "max_unsupported", 703 "leading_comma", 704 "max_text_width", 705 "comments", 706 "dialect", 707 "unsupported_messages", 708 "_escaped_quote_end", 709 "_escaped_identifier_end", 710 "_next_name", 711 "_identifier_start", 712 "_identifier_end", 713 "_quote_json_path_key_using_brackets", 714 ) 715 716 def __init__( 717 self, 718 pretty: t.Optional[bool] = None, 719 identify: str | bool = False, 720 normalize: bool = False, 721 pad: int = 2, 722 indent: int = 2, 723 normalize_functions: t.Optional[str | bool] = None, 724 unsupported_level: ErrorLevel = ErrorLevel.WARN, 725 max_unsupported: int = 3, 726 leading_comma: bool = False, 727 max_text_width: int = 80, 728 comments: bool = True, 729 dialect: DialectType = None, 730 ): 731 import sqlglot 732 from sqlglot.dialects import Dialect 733 734 self.pretty = pretty if pretty is not None else sqlglot.pretty 735 self.identify = identify 736 self.normalize = normalize 737 self.pad = pad 738 self._indent = indent 739 self.unsupported_level = unsupported_level 740 self.max_unsupported = max_unsupported 741 self.leading_comma = leading_comma 742 self.max_text_width = max_text_width 743 self.comments = comments 744 self.dialect = Dialect.get_or_raise(dialect) 745 746 # This is both a Dialect property and a Generator argument, so we prioritize the latter 747 self.normalize_functions = ( 748 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 749 ) 750 751 self.unsupported_messages: t.List[str] = [] 752 self._escaped_quote_end: str = ( 753 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 754 ) 755 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 756 757 self._next_name = name_sequence("_t") 758 759 self._identifier_start = self.dialect.IDENTIFIER_START 760 self._identifier_end = self.dialect.IDENTIFIER_END 761 762 self._quote_json_path_key_using_brackets = True 763 764 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 765 """ 766 Generates the SQL string corresponding to the given syntax tree. 767 768 Args: 769 expression: The syntax tree. 770 copy: Whether to copy the expression. The generator performs mutations so 771 it is safer to copy. 772 773 Returns: 774 The SQL string corresponding to `expression`. 775 """ 776 if copy: 777 expression = expression.copy() 778 779 expression = self.preprocess(expression) 780 781 self.unsupported_messages = [] 782 sql = self.sql(expression).strip() 783 784 if self.pretty: 785 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 786 787 if self.unsupported_level == ErrorLevel.IGNORE: 788 return sql 789 790 if self.unsupported_level == ErrorLevel.WARN: 791 for msg in self.unsupported_messages: 792 logger.warning(msg) 793 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 794 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 795 796 return sql 797 798 def preprocess(self, expression: exp.Expression) -> exp.Expression: 799 """Apply generic preprocessing transformations to a given expression.""" 800 expression = self._move_ctes_to_top_level(expression) 801 802 if self.ENSURE_BOOLS: 803 from sqlglot.transforms import ensure_bools 804 805 expression = ensure_bools(expression) 806 807 return expression 808 809 def _move_ctes_to_top_level(self, expression: E) -> E: 810 if ( 811 not expression.parent 812 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 813 and any(node.parent is not expression for node in expression.find_all(exp.With)) 814 ): 815 from sqlglot.transforms import move_ctes_to_top_level 816 817 expression = move_ctes_to_top_level(expression) 818 return expression 819 820 def unsupported(self, message: str) -> None: 821 if self.unsupported_level == ErrorLevel.IMMEDIATE: 822 raise UnsupportedError(message) 823 self.unsupported_messages.append(message) 824 825 def sep(self, sep: str = " ") -> str: 826 return f"{sep.strip()}\n" if self.pretty else sep 827 828 def seg(self, sql: str, sep: str = " ") -> str: 829 return f"{self.sep(sep)}{sql}" 830 831 def sanitize_comment(self, comment: str) -> str: 832 comment = " " + comment if comment[0].strip() else comment 833 comment = comment + " " if comment[-1].strip() else comment 834 835 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 836 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 837 comment = comment.replace("*/", "* /") 838 839 return comment 840 841 def maybe_comment( 842 self, 843 sql: str, 844 expression: t.Optional[exp.Expression] = None, 845 comments: t.Optional[t.List[str]] = None, 846 separated: bool = False, 847 ) -> str: 848 comments = ( 849 ((expression and expression.comments) if comments is None else comments) # type: ignore 850 if self.comments 851 else None 852 ) 853 854 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 855 return sql 856 857 comments_sql = " ".join( 858 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 859 ) 860 861 if not comments_sql: 862 return sql 863 864 comments_sql = self._replace_line_breaks(comments_sql) 865 866 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 867 return ( 868 f"{self.sep()}{comments_sql}{sql}" 869 if not sql or sql[0].isspace() 870 else f"{comments_sql}{self.sep()}{sql}" 871 ) 872 873 return f"{sql} {comments_sql}" 874 875 def wrap(self, expression: exp.Expression | str) -> str: 876 this_sql = ( 877 self.sql(expression) 878 if isinstance(expression, exp.UNWRAPPED_QUERIES) 879 else self.sql(expression, "this") 880 ) 881 if not this_sql: 882 return "()" 883 884 this_sql = self.indent(this_sql, level=1, pad=0) 885 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 886 887 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 888 original = self.identify 889 self.identify = False 890 result = func(*args, **kwargs) 891 self.identify = original 892 return result 893 894 def normalize_func(self, name: str) -> str: 895 if self.normalize_functions == "upper" or self.normalize_functions is True: 896 return name.upper() 897 if self.normalize_functions == "lower": 898 return name.lower() 899 return name 900 901 def indent( 902 self, 903 sql: str, 904 level: int = 0, 905 pad: t.Optional[int] = None, 906 skip_first: bool = False, 907 skip_last: bool = False, 908 ) -> str: 909 if not self.pretty or not sql: 910 return sql 911 912 pad = self.pad if pad is None else pad 913 lines = sql.split("\n") 914 915 return "\n".join( 916 ( 917 line 918 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 919 else f"{' ' * (level * self._indent + pad)}{line}" 920 ) 921 for i, line in enumerate(lines) 922 ) 923 924 def sql( 925 self, 926 expression: t.Optional[str | exp.Expression], 927 key: t.Optional[str] = None, 928 comment: bool = True, 929 ) -> str: 930 if not expression: 931 return "" 932 933 if isinstance(expression, str): 934 return expression 935 936 if key: 937 value = expression.args.get(key) 938 if value: 939 return self.sql(value) 940 return "" 941 942 transform = self.TRANSFORMS.get(expression.__class__) 943 944 if callable(transform): 945 sql = transform(self, expression) 946 elif isinstance(expression, exp.Expression): 947 exp_handler_name = f"{expression.key}_sql" 948 949 if hasattr(self, exp_handler_name): 950 sql = getattr(self, exp_handler_name)(expression) 951 elif isinstance(expression, exp.Func): 952 sql = self.function_fallback_sql(expression) 953 elif isinstance(expression, exp.Property): 954 sql = self.property_sql(expression) 955 else: 956 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 957 else: 958 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 959 960 return self.maybe_comment(sql, expression) if self.comments and comment else sql 961 962 def uncache_sql(self, expression: exp.Uncache) -> str: 963 table = self.sql(expression, "this") 964 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 965 return f"UNCACHE TABLE{exists_sql} {table}" 966 967 def cache_sql(self, expression: exp.Cache) -> str: 968 lazy = " LAZY" if expression.args.get("lazy") else "" 969 table = self.sql(expression, "this") 970 options = expression.args.get("options") 971 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 972 sql = self.sql(expression, "expression") 973 sql = f" AS{self.sep()}{sql}" if sql else "" 974 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 975 return self.prepend_ctes(expression, sql) 976 977 def characterset_sql(self, expression: exp.CharacterSet) -> str: 978 if isinstance(expression.parent, exp.Cast): 979 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 980 default = "DEFAULT " if expression.args.get("default") else "" 981 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 982 983 def column_parts(self, expression: exp.Column) -> str: 984 return ".".join( 985 self.sql(part) 986 for part in ( 987 expression.args.get("catalog"), 988 expression.args.get("db"), 989 expression.args.get("table"), 990 expression.args.get("this"), 991 ) 992 if part 993 ) 994 995 def column_sql(self, expression: exp.Column) -> str: 996 join_mark = " (+)" if expression.args.get("join_mark") else "" 997 998 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 999 join_mark = "" 1000 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1001 1002 return f"{self.column_parts(expression)}{join_mark}" 1003 1004 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 1005 this = self.sql(expression, "this") 1006 this = f" {this}" if this else "" 1007 position = self.sql(expression, "position") 1008 return f"{position}{this}" 1009 1010 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1011 column = self.sql(expression, "this") 1012 kind = self.sql(expression, "kind") 1013 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1014 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1015 kind = f"{sep}{kind}" if kind else "" 1016 constraints = f" {constraints}" if constraints else "" 1017 position = self.sql(expression, "position") 1018 position = f" {position}" if position else "" 1019 1020 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1021 kind = "" 1022 1023 return f"{exists}{column}{kind}{constraints}{position}" 1024 1025 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1026 this = self.sql(expression, "this") 1027 kind_sql = self.sql(expression, "kind").strip() 1028 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1029 1030 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1031 this = self.sql(expression, "this") 1032 if expression.args.get("not_null"): 1033 persisted = " PERSISTED NOT NULL" 1034 elif expression.args.get("persisted"): 1035 persisted = " PERSISTED" 1036 else: 1037 persisted = "" 1038 1039 return f"AS {this}{persisted}" 1040 1041 def autoincrementcolumnconstraint_sql(self, _) -> str: 1042 return self.token_sql(TokenType.AUTO_INCREMENT) 1043 1044 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1045 if isinstance(expression.this, list): 1046 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1047 else: 1048 this = self.sql(expression, "this") 1049 1050 return f"COMPRESS {this}" 1051 1052 def generatedasidentitycolumnconstraint_sql( 1053 self, expression: exp.GeneratedAsIdentityColumnConstraint 1054 ) -> str: 1055 this = "" 1056 if expression.this is not None: 1057 on_null = " ON NULL" if expression.args.get("on_null") else "" 1058 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1059 1060 start = expression.args.get("start") 1061 start = f"START WITH {start}" if start else "" 1062 increment = expression.args.get("increment") 1063 increment = f" INCREMENT BY {increment}" if increment else "" 1064 minvalue = expression.args.get("minvalue") 1065 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1066 maxvalue = expression.args.get("maxvalue") 1067 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1068 cycle = expression.args.get("cycle") 1069 cycle_sql = "" 1070 1071 if cycle is not None: 1072 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1073 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1074 1075 sequence_opts = "" 1076 if start or increment or cycle_sql: 1077 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1078 sequence_opts = f" ({sequence_opts.strip()})" 1079 1080 expr = self.sql(expression, "expression") 1081 expr = f"({expr})" if expr else "IDENTITY" 1082 1083 return f"GENERATED{this} AS {expr}{sequence_opts}" 1084 1085 def generatedasrowcolumnconstraint_sql( 1086 self, expression: exp.GeneratedAsRowColumnConstraint 1087 ) -> str: 1088 start = "START" if expression.args.get("start") else "END" 1089 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1090 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1091 1092 def periodforsystemtimeconstraint_sql( 1093 self, expression: exp.PeriodForSystemTimeConstraint 1094 ) -> str: 1095 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1096 1097 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1098 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1099 1100 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1101 desc = expression.args.get("desc") 1102 if desc is not None: 1103 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1104 options = self.expressions(expression, key="options", flat=True, sep=" ") 1105 options = f" {options}" if options else "" 1106 return f"PRIMARY KEY{options}" 1107 1108 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1109 this = self.sql(expression, "this") 1110 this = f" {this}" if this else "" 1111 index_type = expression.args.get("index_type") 1112 index_type = f" USING {index_type}" if index_type else "" 1113 on_conflict = self.sql(expression, "on_conflict") 1114 on_conflict = f" {on_conflict}" if on_conflict else "" 1115 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1116 options = self.expressions(expression, key="options", flat=True, sep=" ") 1117 options = f" {options}" if options else "" 1118 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1119 1120 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1121 return self.sql(expression, "this") 1122 1123 def create_sql(self, expression: exp.Create) -> str: 1124 kind = self.sql(expression, "kind") 1125 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1126 properties = expression.args.get("properties") 1127 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1128 1129 this = self.createable_sql(expression, properties_locs) 1130 1131 properties_sql = "" 1132 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1133 exp.Properties.Location.POST_WITH 1134 ): 1135 properties_sql = self.sql( 1136 exp.Properties( 1137 expressions=[ 1138 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1139 *properties_locs[exp.Properties.Location.POST_WITH], 1140 ] 1141 ) 1142 ) 1143 1144 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1145 properties_sql = self.sep() + properties_sql 1146 elif not self.pretty: 1147 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1148 properties_sql = f" {properties_sql}" 1149 1150 begin = " BEGIN" if expression.args.get("begin") else "" 1151 end = " END" if expression.args.get("end") else "" 1152 1153 expression_sql = self.sql(expression, "expression") 1154 if expression_sql: 1155 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1156 1157 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1158 postalias_props_sql = "" 1159 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1160 postalias_props_sql = self.properties( 1161 exp.Properties( 1162 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1163 ), 1164 wrapped=False, 1165 ) 1166 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1167 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1168 1169 postindex_props_sql = "" 1170 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1171 postindex_props_sql = self.properties( 1172 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1173 wrapped=False, 1174 prefix=" ", 1175 ) 1176 1177 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1178 indexes = f" {indexes}" if indexes else "" 1179 index_sql = indexes + postindex_props_sql 1180 1181 replace = " OR REPLACE" if expression.args.get("replace") else "" 1182 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1183 unique = " UNIQUE" if expression.args.get("unique") else "" 1184 1185 clustered = expression.args.get("clustered") 1186 if clustered is None: 1187 clustered_sql = "" 1188 elif clustered: 1189 clustered_sql = " CLUSTERED COLUMNSTORE" 1190 else: 1191 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1192 1193 postcreate_props_sql = "" 1194 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1195 postcreate_props_sql = self.properties( 1196 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1197 sep=" ", 1198 prefix=" ", 1199 wrapped=False, 1200 ) 1201 1202 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1203 1204 postexpression_props_sql = "" 1205 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1206 postexpression_props_sql = self.properties( 1207 exp.Properties( 1208 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1209 ), 1210 sep=" ", 1211 prefix=" ", 1212 wrapped=False, 1213 ) 1214 1215 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1216 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1217 no_schema_binding = ( 1218 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1219 ) 1220 1221 clone = self.sql(expression, "clone") 1222 clone = f" {clone}" if clone else "" 1223 1224 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1225 properties_expression = f"{expression_sql}{properties_sql}" 1226 else: 1227 properties_expression = f"{properties_sql}{expression_sql}" 1228 1229 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1230 return self.prepend_ctes(expression, expression_sql) 1231 1232 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1233 start = self.sql(expression, "start") 1234 start = f"START WITH {start}" if start else "" 1235 increment = self.sql(expression, "increment") 1236 increment = f" INCREMENT BY {increment}" if increment else "" 1237 minvalue = self.sql(expression, "minvalue") 1238 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1239 maxvalue = self.sql(expression, "maxvalue") 1240 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1241 owned = self.sql(expression, "owned") 1242 owned = f" OWNED BY {owned}" if owned else "" 1243 1244 cache = expression.args.get("cache") 1245 if cache is None: 1246 cache_str = "" 1247 elif cache is True: 1248 cache_str = " CACHE" 1249 else: 1250 cache_str = f" CACHE {cache}" 1251 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 1255 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1256 1257 def clone_sql(self, expression: exp.Clone) -> str: 1258 this = self.sql(expression, "this") 1259 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1260 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1261 return f"{shallow}{keyword} {this}" 1262 1263 def describe_sql(self, expression: exp.Describe) -> str: 1264 style = expression.args.get("style") 1265 style = f" {style}" if style else "" 1266 partition = self.sql(expression, "partition") 1267 partition = f" {partition}" if partition else "" 1268 format = self.sql(expression, "format") 1269 format = f" {format}" if format else "" 1270 1271 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1272 1273 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1274 tag = self.sql(expression, "tag") 1275 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1276 1277 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1278 with_ = self.sql(expression, "with") 1279 if with_: 1280 sql = f"{with_}{self.sep()}{sql}" 1281 return sql 1282 1283 def with_sql(self, expression: exp.With) -> str: 1284 sql = self.expressions(expression, flat=True) 1285 recursive = ( 1286 "RECURSIVE " 1287 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1288 else "" 1289 ) 1290 search = self.sql(expression, "search") 1291 search = f" {search}" if search else "" 1292 1293 return f"WITH {recursive}{sql}{search}" 1294 1295 def cte_sql(self, expression: exp.CTE) -> str: 1296 alias = expression.args.get("alias") 1297 if alias: 1298 alias.add_comments(expression.pop_comments()) 1299 1300 alias_sql = self.sql(expression, "alias") 1301 1302 materialized = expression.args.get("materialized") 1303 if materialized is False: 1304 materialized = "NOT MATERIALIZED " 1305 elif materialized: 1306 materialized = "MATERIALIZED " 1307 1308 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1309 1310 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1311 alias = self.sql(expression, "this") 1312 columns = self.expressions(expression, key="columns", flat=True) 1313 columns = f"({columns})" if columns else "" 1314 1315 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1316 columns = "" 1317 self.unsupported("Named columns are not supported in table alias.") 1318 1319 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1320 alias = self._next_name() 1321 1322 return f"{alias}{columns}" 1323 1324 def bitstring_sql(self, expression: exp.BitString) -> str: 1325 this = self.sql(expression, "this") 1326 if self.dialect.BIT_START: 1327 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1328 return f"{int(this, 2)}" 1329 1330 def hexstring_sql( 1331 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1332 ) -> str: 1333 this = self.sql(expression, "this") 1334 is_integer_type = expression.args.get("is_integer") 1335 1336 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1337 not self.dialect.HEX_START and not binary_function_repr 1338 ): 1339 # Integer representation will be returned if: 1340 # - The read dialect treats the hex value as integer literal but not the write 1341 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1342 return f"{int(this, 16)}" 1343 1344 if not is_integer_type: 1345 # Read dialect treats the hex value as BINARY/BLOB 1346 if binary_function_repr: 1347 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1348 return self.func(binary_function_repr, exp.Literal.string(this)) 1349 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1350 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1351 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1352 1353 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1354 1355 def bytestring_sql(self, expression: exp.ByteString) -> str: 1356 this = self.sql(expression, "this") 1357 if self.dialect.BYTE_START: 1358 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1359 return this 1360 1361 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1362 this = self.sql(expression, "this") 1363 escape = expression.args.get("escape") 1364 1365 if self.dialect.UNICODE_START: 1366 escape_substitute = r"\\\1" 1367 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1368 else: 1369 escape_substitute = r"\\u\1" 1370 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1371 1372 if escape: 1373 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1374 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1375 else: 1376 escape_pattern = ESCAPED_UNICODE_RE 1377 escape_sql = "" 1378 1379 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1380 this = escape_pattern.sub(escape_substitute, this) 1381 1382 return f"{left_quote}{this}{right_quote}{escape_sql}" 1383 1384 def rawstring_sql(self, expression: exp.RawString) -> str: 1385 string = expression.this 1386 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1387 string = string.replace("\\", "\\\\") 1388 1389 string = self.escape_str(string, escape_backslash=False) 1390 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1391 1392 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1393 this = self.sql(expression, "this") 1394 specifier = self.sql(expression, "expression") 1395 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1396 return f"{this}{specifier}" 1397 1398 def datatype_sql(self, expression: exp.DataType) -> str: 1399 nested = "" 1400 values = "" 1401 interior = self.expressions(expression, flat=True) 1402 1403 type_value = expression.this 1404 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1405 type_sql = self.sql(expression, "kind") 1406 else: 1407 type_sql = ( 1408 self.TYPE_MAPPING.get(type_value, type_value.value) 1409 if isinstance(type_value, exp.DataType.Type) 1410 else type_value 1411 ) 1412 1413 if interior: 1414 if expression.args.get("nested"): 1415 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1416 if expression.args.get("values") is not None: 1417 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1418 values = self.expressions(expression, key="values", flat=True) 1419 values = f"{delimiters[0]}{values}{delimiters[1]}" 1420 elif type_value == exp.DataType.Type.INTERVAL: 1421 nested = f" {interior}" 1422 else: 1423 nested = f"({interior})" 1424 1425 type_sql = f"{type_sql}{nested}{values}" 1426 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1427 exp.DataType.Type.TIMETZ, 1428 exp.DataType.Type.TIMESTAMPTZ, 1429 ): 1430 type_sql = f"{type_sql} WITH TIME ZONE" 1431 1432 return type_sql 1433 1434 def directory_sql(self, expression: exp.Directory) -> str: 1435 local = "LOCAL " if expression.args.get("local") else "" 1436 row_format = self.sql(expression, "row_format") 1437 row_format = f" {row_format}" if row_format else "" 1438 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1439 1440 def delete_sql(self, expression: exp.Delete) -> str: 1441 this = self.sql(expression, "this") 1442 this = f" FROM {this}" if this else "" 1443 using = self.sql(expression, "using") 1444 using = f" USING {using}" if using else "" 1445 cluster = self.sql(expression, "cluster") 1446 cluster = f" {cluster}" if cluster else "" 1447 where = self.sql(expression, "where") 1448 returning = self.sql(expression, "returning") 1449 limit = self.sql(expression, "limit") 1450 tables = self.expressions(expression, key="tables") 1451 tables = f" {tables}" if tables else "" 1452 if self.RETURNING_END: 1453 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1454 else: 1455 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1456 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1457 1458 def drop_sql(self, expression: exp.Drop) -> str: 1459 this = self.sql(expression, "this") 1460 expressions = self.expressions(expression, flat=True) 1461 expressions = f" ({expressions})" if expressions else "" 1462 kind = expression.args["kind"] 1463 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1464 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1465 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1466 on_cluster = self.sql(expression, "cluster") 1467 on_cluster = f" {on_cluster}" if on_cluster else "" 1468 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1469 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1470 cascade = " CASCADE" if expression.args.get("cascade") else "" 1471 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1472 purge = " PURGE" if expression.args.get("purge") else "" 1473 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1474 1475 def set_operation(self, expression: exp.SetOperation) -> str: 1476 op_type = type(expression) 1477 op_name = op_type.key.upper() 1478 1479 distinct = expression.args.get("distinct") 1480 if ( 1481 distinct is False 1482 and op_type in (exp.Except, exp.Intersect) 1483 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1484 ): 1485 self.unsupported(f"{op_name} ALL is not supported") 1486 1487 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1488 1489 if distinct is None: 1490 distinct = default_distinct 1491 if distinct is None: 1492 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1493 1494 if distinct is default_distinct: 1495 distinct_or_all = "" 1496 else: 1497 distinct_or_all = " DISTINCT" if distinct else " ALL" 1498 1499 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1500 side_kind = f"{side_kind} " if side_kind else "" 1501 1502 by_name = " BY NAME" if expression.args.get("by_name") else "" 1503 on = self.expressions(expression, key="on", flat=True) 1504 on = f" ON ({on})" if on else "" 1505 1506 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1507 1508 def set_operations(self, expression: exp.SetOperation) -> str: 1509 if not self.SET_OP_MODIFIERS: 1510 limit = expression.args.get("limit") 1511 order = expression.args.get("order") 1512 1513 if limit or order: 1514 select = self._move_ctes_to_top_level( 1515 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1516 ) 1517 1518 if limit: 1519 select = select.limit(limit.pop(), copy=False) 1520 if order: 1521 select = select.order_by(order.pop(), copy=False) 1522 return self.sql(select) 1523 1524 sqls: t.List[str] = [] 1525 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1526 1527 while stack: 1528 node = stack.pop() 1529 1530 if isinstance(node, exp.SetOperation): 1531 stack.append(node.expression) 1532 stack.append( 1533 self.maybe_comment( 1534 self.set_operation(node), comments=node.comments, separated=True 1535 ) 1536 ) 1537 stack.append(node.this) 1538 else: 1539 sqls.append(self.sql(node)) 1540 1541 this = self.sep().join(sqls) 1542 this = self.query_modifiers(expression, this) 1543 return self.prepend_ctes(expression, this) 1544 1545 def fetch_sql(self, expression: exp.Fetch) -> str: 1546 direction = expression.args.get("direction") 1547 direction = f" {direction}" if direction else "" 1548 count = self.sql(expression, "count") 1549 count = f" {count}" if count else "" 1550 limit_options = self.sql(expression, "limit_options") 1551 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1552 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1553 1554 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1555 percent = " PERCENT" if expression.args.get("percent") else "" 1556 rows = " ROWS" if expression.args.get("rows") else "" 1557 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1558 if not with_ties and rows: 1559 with_ties = " ONLY" 1560 return f"{percent}{rows}{with_ties}" 1561 1562 def filter_sql(self, expression: exp.Filter) -> str: 1563 if self.AGGREGATE_FILTER_SUPPORTED: 1564 this = self.sql(expression, "this") 1565 where = self.sql(expression, "expression").strip() 1566 return f"{this} FILTER({where})" 1567 1568 agg = expression.this 1569 agg_arg = agg.this 1570 cond = expression.expression.this 1571 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1572 return self.sql(agg) 1573 1574 def hint_sql(self, expression: exp.Hint) -> str: 1575 if not self.QUERY_HINTS: 1576 self.unsupported("Hints are not supported") 1577 return "" 1578 1579 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1580 1581 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1582 using = self.sql(expression, "using") 1583 using = f" USING {using}" if using else "" 1584 columns = self.expressions(expression, key="columns", flat=True) 1585 columns = f"({columns})" if columns else "" 1586 partition_by = self.expressions(expression, key="partition_by", flat=True) 1587 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1588 where = self.sql(expression, "where") 1589 include = self.expressions(expression, key="include", flat=True) 1590 if include: 1591 include = f" INCLUDE ({include})" 1592 with_storage = self.expressions(expression, key="with_storage", flat=True) 1593 with_storage = f" WITH ({with_storage})" if with_storage else "" 1594 tablespace = self.sql(expression, "tablespace") 1595 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1596 on = self.sql(expression, "on") 1597 on = f" ON {on}" if on else "" 1598 1599 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1600 1601 def index_sql(self, expression: exp.Index) -> str: 1602 unique = "UNIQUE " if expression.args.get("unique") else "" 1603 primary = "PRIMARY " if expression.args.get("primary") else "" 1604 amp = "AMP " if expression.args.get("amp") else "" 1605 name = self.sql(expression, "this") 1606 name = f"{name} " if name else "" 1607 table = self.sql(expression, "table") 1608 table = f"{self.INDEX_ON} {table}" if table else "" 1609 1610 index = "INDEX " if not table else "" 1611 1612 params = self.sql(expression, "params") 1613 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1614 1615 def identifier_sql(self, expression: exp.Identifier) -> str: 1616 text = expression.name 1617 lower = text.lower() 1618 text = lower if self.normalize and not expression.quoted else text 1619 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1620 if ( 1621 expression.quoted 1622 or self.dialect.can_identify(text, self.identify) 1623 or lower in self.RESERVED_KEYWORDS 1624 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1625 ): 1626 text = f"{self._identifier_start}{text}{self._identifier_end}" 1627 return text 1628 1629 def hex_sql(self, expression: exp.Hex) -> str: 1630 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1631 if self.dialect.HEX_LOWERCASE: 1632 text = self.func("LOWER", text) 1633 1634 return text 1635 1636 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1637 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1638 if not self.dialect.HEX_LOWERCASE: 1639 text = self.func("LOWER", text) 1640 return text 1641 1642 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1643 input_format = self.sql(expression, "input_format") 1644 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1645 output_format = self.sql(expression, "output_format") 1646 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1647 return self.sep().join((input_format, output_format)) 1648 1649 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1650 string = self.sql(exp.Literal.string(expression.name)) 1651 return f"{prefix}{string}" 1652 1653 def partition_sql(self, expression: exp.Partition) -> str: 1654 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1655 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1656 1657 def properties_sql(self, expression: exp.Properties) -> str: 1658 root_properties = [] 1659 with_properties = [] 1660 1661 for p in expression.expressions: 1662 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1663 if p_loc == exp.Properties.Location.POST_WITH: 1664 with_properties.append(p) 1665 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1666 root_properties.append(p) 1667 1668 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1669 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1670 1671 if root_props and with_props and not self.pretty: 1672 with_props = " " + with_props 1673 1674 return root_props + with_props 1675 1676 def root_properties(self, properties: exp.Properties) -> str: 1677 if properties.expressions: 1678 return self.expressions(properties, indent=False, sep=" ") 1679 return "" 1680 1681 def properties( 1682 self, 1683 properties: exp.Properties, 1684 prefix: str = "", 1685 sep: str = ", ", 1686 suffix: str = "", 1687 wrapped: bool = True, 1688 ) -> str: 1689 if properties.expressions: 1690 expressions = self.expressions(properties, sep=sep, indent=False) 1691 if expressions: 1692 expressions = self.wrap(expressions) if wrapped else expressions 1693 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1694 return "" 1695 1696 def with_properties(self, properties: exp.Properties) -> str: 1697 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1698 1699 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1700 properties_locs = defaultdict(list) 1701 for p in properties.expressions: 1702 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1703 if p_loc != exp.Properties.Location.UNSUPPORTED: 1704 properties_locs[p_loc].append(p) 1705 else: 1706 self.unsupported(f"Unsupported property {p.key}") 1707 1708 return properties_locs 1709 1710 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1711 if isinstance(expression.this, exp.Dot): 1712 return self.sql(expression, "this") 1713 return f"'{expression.name}'" if string_key else expression.name 1714 1715 def property_sql(self, expression: exp.Property) -> str: 1716 property_cls = expression.__class__ 1717 if property_cls == exp.Property: 1718 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1719 1720 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1721 if not property_name: 1722 self.unsupported(f"Unsupported property {expression.key}") 1723 1724 return f"{property_name}={self.sql(expression, 'this')}" 1725 1726 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1727 if self.SUPPORTS_CREATE_TABLE_LIKE: 1728 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1729 options = f" {options}" if options else "" 1730 1731 like = f"LIKE {self.sql(expression, 'this')}{options}" 1732 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1733 like = f"({like})" 1734 1735 return like 1736 1737 if expression.expressions: 1738 self.unsupported("Transpilation of LIKE property options is unsupported") 1739 1740 select = exp.select("*").from_(expression.this).limit(0) 1741 return f"AS {self.sql(select)}" 1742 1743 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1744 no = "NO " if expression.args.get("no") else "" 1745 protection = " PROTECTION" if expression.args.get("protection") else "" 1746 return f"{no}FALLBACK{protection}" 1747 1748 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1749 no = "NO " if expression.args.get("no") else "" 1750 local = expression.args.get("local") 1751 local = f"{local} " if local else "" 1752 dual = "DUAL " if expression.args.get("dual") else "" 1753 before = "BEFORE " if expression.args.get("before") else "" 1754 after = "AFTER " if expression.args.get("after") else "" 1755 return f"{no}{local}{dual}{before}{after}JOURNAL" 1756 1757 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1758 freespace = self.sql(expression, "this") 1759 percent = " PERCENT" if expression.args.get("percent") else "" 1760 return f"FREESPACE={freespace}{percent}" 1761 1762 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1763 if expression.args.get("default"): 1764 property = "DEFAULT" 1765 elif expression.args.get("on"): 1766 property = "ON" 1767 else: 1768 property = "OFF" 1769 return f"CHECKSUM={property}" 1770 1771 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1772 if expression.args.get("no"): 1773 return "NO MERGEBLOCKRATIO" 1774 if expression.args.get("default"): 1775 return "DEFAULT MERGEBLOCKRATIO" 1776 1777 percent = " PERCENT" if expression.args.get("percent") else "" 1778 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1779 1780 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1781 default = expression.args.get("default") 1782 minimum = expression.args.get("minimum") 1783 maximum = expression.args.get("maximum") 1784 if default or minimum or maximum: 1785 if default: 1786 prop = "DEFAULT" 1787 elif minimum: 1788 prop = "MINIMUM" 1789 else: 1790 prop = "MAXIMUM" 1791 return f"{prop} DATABLOCKSIZE" 1792 units = expression.args.get("units") 1793 units = f" {units}" if units else "" 1794 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1795 1796 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1797 autotemp = expression.args.get("autotemp") 1798 always = expression.args.get("always") 1799 default = expression.args.get("default") 1800 manual = expression.args.get("manual") 1801 never = expression.args.get("never") 1802 1803 if autotemp is not None: 1804 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1805 elif always: 1806 prop = "ALWAYS" 1807 elif default: 1808 prop = "DEFAULT" 1809 elif manual: 1810 prop = "MANUAL" 1811 elif never: 1812 prop = "NEVER" 1813 return f"BLOCKCOMPRESSION={prop}" 1814 1815 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1816 no = expression.args.get("no") 1817 no = " NO" if no else "" 1818 concurrent = expression.args.get("concurrent") 1819 concurrent = " CONCURRENT" if concurrent else "" 1820 target = self.sql(expression, "target") 1821 target = f" {target}" if target else "" 1822 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1823 1824 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1825 if isinstance(expression.this, list): 1826 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1827 if expression.this: 1828 modulus = self.sql(expression, "this") 1829 remainder = self.sql(expression, "expression") 1830 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1831 1832 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1833 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1834 return f"FROM ({from_expressions}) TO ({to_expressions})" 1835 1836 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1837 this = self.sql(expression, "this") 1838 1839 for_values_or_default = expression.expression 1840 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1841 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1842 else: 1843 for_values_or_default = " DEFAULT" 1844 1845 return f"PARTITION OF {this}{for_values_or_default}" 1846 1847 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1848 kind = expression.args.get("kind") 1849 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1850 for_or_in = expression.args.get("for_or_in") 1851 for_or_in = f" {for_or_in}" if for_or_in else "" 1852 lock_type = expression.args.get("lock_type") 1853 override = " OVERRIDE" if expression.args.get("override") else "" 1854 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1855 1856 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1857 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1858 statistics = expression.args.get("statistics") 1859 statistics_sql = "" 1860 if statistics is not None: 1861 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1862 return f"{data_sql}{statistics_sql}" 1863 1864 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1865 this = self.sql(expression, "this") 1866 this = f"HISTORY_TABLE={this}" if this else "" 1867 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1868 data_consistency = ( 1869 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1870 ) 1871 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1872 retention_period = ( 1873 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1874 ) 1875 1876 if this: 1877 on_sql = self.func("ON", this, data_consistency, retention_period) 1878 else: 1879 on_sql = "ON" if expression.args.get("on") else "OFF" 1880 1881 sql = f"SYSTEM_VERSIONING={on_sql}" 1882 1883 return f"WITH({sql})" if expression.args.get("with") else sql 1884 1885 def insert_sql(self, expression: exp.Insert) -> str: 1886 hint = self.sql(expression, "hint") 1887 overwrite = expression.args.get("overwrite") 1888 1889 if isinstance(expression.this, exp.Directory): 1890 this = " OVERWRITE" if overwrite else " INTO" 1891 else: 1892 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1893 1894 stored = self.sql(expression, "stored") 1895 stored = f" {stored}" if stored else "" 1896 alternative = expression.args.get("alternative") 1897 alternative = f" OR {alternative}" if alternative else "" 1898 ignore = " IGNORE" if expression.args.get("ignore") else "" 1899 is_function = expression.args.get("is_function") 1900 if is_function: 1901 this = f"{this} FUNCTION" 1902 this = f"{this} {self.sql(expression, 'this')}" 1903 1904 exists = " IF EXISTS" if expression.args.get("exists") else "" 1905 where = self.sql(expression, "where") 1906 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1907 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1908 on_conflict = self.sql(expression, "conflict") 1909 on_conflict = f" {on_conflict}" if on_conflict else "" 1910 by_name = " BY NAME" if expression.args.get("by_name") else "" 1911 returning = self.sql(expression, "returning") 1912 1913 if self.RETURNING_END: 1914 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1915 else: 1916 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1917 1918 partition_by = self.sql(expression, "partition") 1919 partition_by = f" {partition_by}" if partition_by else "" 1920 settings = self.sql(expression, "settings") 1921 settings = f" {settings}" if settings else "" 1922 1923 source = self.sql(expression, "source") 1924 source = f"TABLE {source}" if source else "" 1925 1926 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1927 return self.prepend_ctes(expression, sql) 1928 1929 def introducer_sql(self, expression: exp.Introducer) -> str: 1930 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1931 1932 def kill_sql(self, expression: exp.Kill) -> str: 1933 kind = self.sql(expression, "kind") 1934 kind = f" {kind}" if kind else "" 1935 this = self.sql(expression, "this") 1936 this = f" {this}" if this else "" 1937 return f"KILL{kind}{this}" 1938 1939 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1940 return expression.name 1941 1942 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1943 return expression.name 1944 1945 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1946 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1947 1948 constraint = self.sql(expression, "constraint") 1949 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1950 1951 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1952 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1953 action = self.sql(expression, "action") 1954 1955 expressions = self.expressions(expression, flat=True) 1956 if expressions: 1957 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1958 expressions = f" {set_keyword}{expressions}" 1959 1960 where = self.sql(expression, "where") 1961 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1962 1963 def returning_sql(self, expression: exp.Returning) -> str: 1964 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1965 1966 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1967 fields = self.sql(expression, "fields") 1968 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1969 escaped = self.sql(expression, "escaped") 1970 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1971 items = self.sql(expression, "collection_items") 1972 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1973 keys = self.sql(expression, "map_keys") 1974 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1975 lines = self.sql(expression, "lines") 1976 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1977 null = self.sql(expression, "null") 1978 null = f" NULL DEFINED AS {null}" if null else "" 1979 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1980 1981 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1982 return f"WITH ({self.expressions(expression, flat=True)})" 1983 1984 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1985 this = f"{self.sql(expression, 'this')} INDEX" 1986 target = self.sql(expression, "target") 1987 target = f" FOR {target}" if target else "" 1988 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1989 1990 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1991 this = self.sql(expression, "this") 1992 kind = self.sql(expression, "kind") 1993 expr = self.sql(expression, "expression") 1994 return f"{this} ({kind} => {expr})" 1995 1996 def table_parts(self, expression: exp.Table) -> str: 1997 return ".".join( 1998 self.sql(part) 1999 for part in ( 2000 expression.args.get("catalog"), 2001 expression.args.get("db"), 2002 expression.args.get("this"), 2003 ) 2004 if part is not None 2005 ) 2006 2007 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2008 table = self.table_parts(expression) 2009 only = "ONLY " if expression.args.get("only") else "" 2010 partition = self.sql(expression, "partition") 2011 partition = f" {partition}" if partition else "" 2012 version = self.sql(expression, "version") 2013 version = f" {version}" if version else "" 2014 alias = self.sql(expression, "alias") 2015 alias = f"{sep}{alias}" if alias else "" 2016 2017 sample = self.sql(expression, "sample") 2018 if self.dialect.ALIAS_POST_TABLESAMPLE: 2019 sample_pre_alias = sample 2020 sample_post_alias = "" 2021 else: 2022 sample_pre_alias = "" 2023 sample_post_alias = sample 2024 2025 hints = self.expressions(expression, key="hints", sep=" ") 2026 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2027 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2028 joins = self.indent( 2029 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2030 ) 2031 laterals = self.expressions(expression, key="laterals", sep="") 2032 2033 file_format = self.sql(expression, "format") 2034 if file_format: 2035 pattern = self.sql(expression, "pattern") 2036 pattern = f", PATTERN => {pattern}" if pattern else "" 2037 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2038 2039 ordinality = expression.args.get("ordinality") or "" 2040 if ordinality: 2041 ordinality = f" WITH ORDINALITY{alias}" 2042 alias = "" 2043 2044 when = self.sql(expression, "when") 2045 if when: 2046 table = f"{table} {when}" 2047 2048 changes = self.sql(expression, "changes") 2049 changes = f" {changes}" if changes else "" 2050 2051 rows_from = self.expressions(expression, key="rows_from") 2052 if rows_from: 2053 table = f"ROWS FROM {self.wrap(rows_from)}" 2054 2055 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2056 2057 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2058 table = self.func("TABLE", expression.this) 2059 alias = self.sql(expression, "alias") 2060 alias = f" AS {alias}" if alias else "" 2061 sample = self.sql(expression, "sample") 2062 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2063 joins = self.indent( 2064 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2065 ) 2066 return f"{table}{alias}{pivots}{sample}{joins}" 2067 2068 def tablesample_sql( 2069 self, 2070 expression: exp.TableSample, 2071 tablesample_keyword: t.Optional[str] = None, 2072 ) -> str: 2073 method = self.sql(expression, "method") 2074 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2075 numerator = self.sql(expression, "bucket_numerator") 2076 denominator = self.sql(expression, "bucket_denominator") 2077 field = self.sql(expression, "bucket_field") 2078 field = f" ON {field}" if field else "" 2079 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2080 seed = self.sql(expression, "seed") 2081 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2082 2083 size = self.sql(expression, "size") 2084 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2085 size = f"{size} ROWS" 2086 2087 percent = self.sql(expression, "percent") 2088 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2089 percent = f"{percent} PERCENT" 2090 2091 expr = f"{bucket}{percent}{size}" 2092 if self.TABLESAMPLE_REQUIRES_PARENS: 2093 expr = f"({expr})" 2094 2095 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2096 2097 def pivot_sql(self, expression: exp.Pivot) -> str: 2098 expressions = self.expressions(expression, flat=True) 2099 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2100 2101 group = self.sql(expression, "group") 2102 2103 if expression.this: 2104 this = self.sql(expression, "this") 2105 if not expressions: 2106 return f"UNPIVOT {this}" 2107 2108 on = f"{self.seg('ON')} {expressions}" 2109 into = self.sql(expression, "into") 2110 into = f"{self.seg('INTO')} {into}" if into else "" 2111 using = self.expressions(expression, key="using", flat=True) 2112 using = f"{self.seg('USING')} {using}" if using else "" 2113 return f"{direction} {this}{on}{into}{using}{group}" 2114 2115 alias = self.sql(expression, "alias") 2116 alias = f" AS {alias}" if alias else "" 2117 2118 fields = self.expressions( 2119 expression, 2120 "fields", 2121 sep=" ", 2122 dynamic=True, 2123 new_line=True, 2124 skip_first=True, 2125 skip_last=True, 2126 ) 2127 2128 include_nulls = expression.args.get("include_nulls") 2129 if include_nulls is not None: 2130 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2131 else: 2132 nulls = "" 2133 2134 default_on_null = self.sql(expression, "default_on_null") 2135 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2136 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2137 2138 def version_sql(self, expression: exp.Version) -> str: 2139 this = f"FOR {expression.name}" 2140 kind = expression.text("kind") 2141 expr = self.sql(expression, "expression") 2142 return f"{this} {kind} {expr}" 2143 2144 def tuple_sql(self, expression: exp.Tuple) -> str: 2145 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2146 2147 def update_sql(self, expression: exp.Update) -> str: 2148 this = self.sql(expression, "this") 2149 set_sql = self.expressions(expression, flat=True) 2150 from_sql = self.sql(expression, "from") 2151 where_sql = self.sql(expression, "where") 2152 returning = self.sql(expression, "returning") 2153 order = self.sql(expression, "order") 2154 limit = self.sql(expression, "limit") 2155 if self.RETURNING_END: 2156 expression_sql = f"{from_sql}{where_sql}{returning}" 2157 else: 2158 expression_sql = f"{returning}{from_sql}{where_sql}" 2159 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2160 return self.prepend_ctes(expression, sql) 2161 2162 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2163 values_as_table = values_as_table and self.VALUES_AS_TABLE 2164 2165 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2166 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2167 args = self.expressions(expression) 2168 alias = self.sql(expression, "alias") 2169 values = f"VALUES{self.seg('')}{args}" 2170 values = ( 2171 f"({values})" 2172 if self.WRAP_DERIVED_VALUES 2173 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2174 else values 2175 ) 2176 return f"{values} AS {alias}" if alias else values 2177 2178 # Converts `VALUES...` expression into a series of select unions. 2179 alias_node = expression.args.get("alias") 2180 column_names = alias_node and alias_node.columns 2181 2182 selects: t.List[exp.Query] = [] 2183 2184 for i, tup in enumerate(expression.expressions): 2185 row = tup.expressions 2186 2187 if i == 0 and column_names: 2188 row = [ 2189 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2190 ] 2191 2192 selects.append(exp.Select(expressions=row)) 2193 2194 if self.pretty: 2195 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2196 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2197 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2198 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2199 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2200 2201 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2202 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2203 return f"({unions}){alias}" 2204 2205 def var_sql(self, expression: exp.Var) -> str: 2206 return self.sql(expression, "this") 2207 2208 @unsupported_args("expressions") 2209 def into_sql(self, expression: exp.Into) -> str: 2210 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2211 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2212 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2213 2214 def from_sql(self, expression: exp.From) -> str: 2215 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2216 2217 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2218 grouping_sets = self.expressions(expression, indent=False) 2219 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2220 2221 def rollup_sql(self, expression: exp.Rollup) -> str: 2222 expressions = self.expressions(expression, indent=False) 2223 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2224 2225 def cube_sql(self, expression: exp.Cube) -> str: 2226 expressions = self.expressions(expression, indent=False) 2227 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2228 2229 def group_sql(self, expression: exp.Group) -> str: 2230 group_by_all = expression.args.get("all") 2231 if group_by_all is True: 2232 modifier = " ALL" 2233 elif group_by_all is False: 2234 modifier = " DISTINCT" 2235 else: 2236 modifier = "" 2237 2238 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2239 2240 grouping_sets = self.expressions(expression, key="grouping_sets") 2241 cube = self.expressions(expression, key="cube") 2242 rollup = self.expressions(expression, key="rollup") 2243 2244 groupings = csv( 2245 self.seg(grouping_sets) if grouping_sets else "", 2246 self.seg(cube) if cube else "", 2247 self.seg(rollup) if rollup else "", 2248 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2249 sep=self.GROUPINGS_SEP, 2250 ) 2251 2252 if ( 2253 expression.expressions 2254 and groupings 2255 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2256 ): 2257 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2258 2259 return f"{group_by}{groupings}" 2260 2261 def having_sql(self, expression: exp.Having) -> str: 2262 this = self.indent(self.sql(expression, "this")) 2263 return f"{self.seg('HAVING')}{self.sep()}{this}" 2264 2265 def connect_sql(self, expression: exp.Connect) -> str: 2266 start = self.sql(expression, "start") 2267 start = self.seg(f"START WITH {start}") if start else "" 2268 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2269 connect = self.sql(expression, "connect") 2270 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2271 return start + connect 2272 2273 def prior_sql(self, expression: exp.Prior) -> str: 2274 return f"PRIOR {self.sql(expression, 'this')}" 2275 2276 def join_sql(self, expression: exp.Join) -> str: 2277 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2278 side = None 2279 else: 2280 side = expression.side 2281 2282 op_sql = " ".join( 2283 op 2284 for op in ( 2285 expression.method, 2286 "GLOBAL" if expression.args.get("global") else None, 2287 side, 2288 expression.kind, 2289 expression.hint if self.JOIN_HINTS else None, 2290 ) 2291 if op 2292 ) 2293 match_cond = self.sql(expression, "match_condition") 2294 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2295 on_sql = self.sql(expression, "on") 2296 using = expression.args.get("using") 2297 2298 if not on_sql and using: 2299 on_sql = csv(*(self.sql(column) for column in using)) 2300 2301 this = expression.this 2302 this_sql = self.sql(this) 2303 2304 exprs = self.expressions(expression) 2305 if exprs: 2306 this_sql = f"{this_sql},{self.seg(exprs)}" 2307 2308 if on_sql: 2309 on_sql = self.indent(on_sql, skip_first=True) 2310 space = self.seg(" " * self.pad) if self.pretty else " " 2311 if using: 2312 on_sql = f"{space}USING ({on_sql})" 2313 else: 2314 on_sql = f"{space}ON {on_sql}" 2315 elif not op_sql: 2316 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2317 return f" {this_sql}" 2318 2319 return f", {this_sql}" 2320 2321 if op_sql != "STRAIGHT_JOIN": 2322 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2323 2324 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2325 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2326 2327 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->", wrap: bool = True) -> str: 2328 args = self.expressions(expression, flat=True) 2329 args = f"({args})" if wrap and len(args.split(",")) > 1 else args 2330 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2331 2332 def lateral_op(self, expression: exp.Lateral) -> str: 2333 cross_apply = expression.args.get("cross_apply") 2334 2335 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2336 if cross_apply is True: 2337 op = "INNER JOIN " 2338 elif cross_apply is False: 2339 op = "LEFT JOIN " 2340 else: 2341 op = "" 2342 2343 return f"{op}LATERAL" 2344 2345 def lateral_sql(self, expression: exp.Lateral) -> str: 2346 this = self.sql(expression, "this") 2347 2348 if expression.args.get("view"): 2349 alias = expression.args["alias"] 2350 columns = self.expressions(alias, key="columns", flat=True) 2351 table = f" {alias.name}" if alias.name else "" 2352 columns = f" AS {columns}" if columns else "" 2353 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2354 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2355 2356 alias = self.sql(expression, "alias") 2357 alias = f" AS {alias}" if alias else "" 2358 2359 ordinality = expression.args.get("ordinality") or "" 2360 if ordinality: 2361 ordinality = f" WITH ORDINALITY{alias}" 2362 alias = "" 2363 2364 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2365 2366 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2367 this = self.sql(expression, "this") 2368 2369 args = [ 2370 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2371 for e in (expression.args.get(k) for k in ("offset", "expression")) 2372 if e 2373 ] 2374 2375 args_sql = ", ".join(self.sql(e) for e in args) 2376 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2377 expressions = self.expressions(expression, flat=True) 2378 limit_options = self.sql(expression, "limit_options") 2379 expressions = f" BY {expressions}" if expressions else "" 2380 2381 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2382 2383 def offset_sql(self, expression: exp.Offset) -> str: 2384 this = self.sql(expression, "this") 2385 value = expression.expression 2386 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2387 expressions = self.expressions(expression, flat=True) 2388 expressions = f" BY {expressions}" if expressions else "" 2389 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2390 2391 def setitem_sql(self, expression: exp.SetItem) -> str: 2392 kind = self.sql(expression, "kind") 2393 kind = f"{kind} " if kind else "" 2394 this = self.sql(expression, "this") 2395 expressions = self.expressions(expression) 2396 collate = self.sql(expression, "collate") 2397 collate = f" COLLATE {collate}" if collate else "" 2398 global_ = "GLOBAL " if expression.args.get("global") else "" 2399 return f"{global_}{kind}{this}{expressions}{collate}" 2400 2401 def set_sql(self, expression: exp.Set) -> str: 2402 expressions = f" {self.expressions(expression, flat=True)}" 2403 tag = " TAG" if expression.args.get("tag") else "" 2404 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2405 2406 def pragma_sql(self, expression: exp.Pragma) -> str: 2407 return f"PRAGMA {self.sql(expression, 'this')}" 2408 2409 def lock_sql(self, expression: exp.Lock) -> str: 2410 if not self.LOCKING_READS_SUPPORTED: 2411 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2412 return "" 2413 2414 update = expression.args["update"] 2415 key = expression.args.get("key") 2416 if update: 2417 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2418 else: 2419 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2420 expressions = self.expressions(expression, flat=True) 2421 expressions = f" OF {expressions}" if expressions else "" 2422 wait = expression.args.get("wait") 2423 2424 if wait is not None: 2425 if isinstance(wait, exp.Literal): 2426 wait = f" WAIT {self.sql(wait)}" 2427 else: 2428 wait = " NOWAIT" if wait else " SKIP LOCKED" 2429 2430 return f"{lock_type}{expressions}{wait or ''}" 2431 2432 def literal_sql(self, expression: exp.Literal) -> str: 2433 text = expression.this or "" 2434 if expression.is_string: 2435 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2436 return text 2437 2438 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2439 if self.dialect.ESCAPED_SEQUENCES: 2440 to_escaped = self.dialect.ESCAPED_SEQUENCES 2441 text = "".join( 2442 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2443 ) 2444 2445 return self._replace_line_breaks(text).replace( 2446 self.dialect.QUOTE_END, self._escaped_quote_end 2447 ) 2448 2449 def loaddata_sql(self, expression: exp.LoadData) -> str: 2450 local = " LOCAL" if expression.args.get("local") else "" 2451 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2452 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2453 this = f" INTO TABLE {self.sql(expression, 'this')}" 2454 partition = self.sql(expression, "partition") 2455 partition = f" {partition}" if partition else "" 2456 input_format = self.sql(expression, "input_format") 2457 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2458 serde = self.sql(expression, "serde") 2459 serde = f" SERDE {serde}" if serde else "" 2460 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2461 2462 def null_sql(self, *_) -> str: 2463 return "NULL" 2464 2465 def boolean_sql(self, expression: exp.Boolean) -> str: 2466 return "TRUE" if expression.this else "FALSE" 2467 2468 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2469 this = self.sql(expression, "this") 2470 this = f"{this} " if this else this 2471 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2472 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2473 2474 def withfill_sql(self, expression: exp.WithFill) -> str: 2475 from_sql = self.sql(expression, "from") 2476 from_sql = f" FROM {from_sql}" if from_sql else "" 2477 to_sql = self.sql(expression, "to") 2478 to_sql = f" TO {to_sql}" if to_sql else "" 2479 step_sql = self.sql(expression, "step") 2480 step_sql = f" STEP {step_sql}" if step_sql else "" 2481 interpolated_values = [ 2482 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2483 if isinstance(e, exp.Alias) 2484 else self.sql(e, "this") 2485 for e in expression.args.get("interpolate") or [] 2486 ] 2487 interpolate = ( 2488 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2489 ) 2490 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2491 2492 def cluster_sql(self, expression: exp.Cluster) -> str: 2493 return self.op_expressions("CLUSTER BY", expression) 2494 2495 def distribute_sql(self, expression: exp.Distribute) -> str: 2496 return self.op_expressions("DISTRIBUTE BY", expression) 2497 2498 def sort_sql(self, expression: exp.Sort) -> str: 2499 return self.op_expressions("SORT BY", expression) 2500 2501 def ordered_sql(self, expression: exp.Ordered) -> str: 2502 desc = expression.args.get("desc") 2503 asc = not desc 2504 2505 nulls_first = expression.args.get("nulls_first") 2506 nulls_last = not nulls_first 2507 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2508 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2509 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2510 2511 this = self.sql(expression, "this") 2512 2513 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2514 nulls_sort_change = "" 2515 if nulls_first and ( 2516 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2517 ): 2518 nulls_sort_change = " NULLS FIRST" 2519 elif ( 2520 nulls_last 2521 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2522 and not nulls_are_last 2523 ): 2524 nulls_sort_change = " NULLS LAST" 2525 2526 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2527 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2528 window = expression.find_ancestor(exp.Window, exp.Select) 2529 if isinstance(window, exp.Window) and window.args.get("spec"): 2530 self.unsupported( 2531 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2532 ) 2533 nulls_sort_change = "" 2534 elif self.NULL_ORDERING_SUPPORTED is False and ( 2535 (asc and nulls_sort_change == " NULLS LAST") 2536 or (desc and nulls_sort_change == " NULLS FIRST") 2537 ): 2538 # BigQuery does not allow these ordering/nulls combinations when used under 2539 # an aggregation func or under a window containing one 2540 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2541 2542 if isinstance(ancestor, exp.Window): 2543 ancestor = ancestor.this 2544 if isinstance(ancestor, exp.AggFunc): 2545 self.unsupported( 2546 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2547 ) 2548 nulls_sort_change = "" 2549 elif self.NULL_ORDERING_SUPPORTED is None: 2550 if expression.this.is_int: 2551 self.unsupported( 2552 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2553 ) 2554 elif not isinstance(expression.this, exp.Rand): 2555 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2556 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2557 nulls_sort_change = "" 2558 2559 with_fill = self.sql(expression, "with_fill") 2560 with_fill = f" {with_fill}" if with_fill else "" 2561 2562 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2563 2564 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2565 window_frame = self.sql(expression, "window_frame") 2566 window_frame = f"{window_frame} " if window_frame else "" 2567 2568 this = self.sql(expression, "this") 2569 2570 return f"{window_frame}{this}" 2571 2572 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2573 partition = self.partition_by_sql(expression) 2574 order = self.sql(expression, "order") 2575 measures = self.expressions(expression, key="measures") 2576 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2577 rows = self.sql(expression, "rows") 2578 rows = self.seg(rows) if rows else "" 2579 after = self.sql(expression, "after") 2580 after = self.seg(after) if after else "" 2581 pattern = self.sql(expression, "pattern") 2582 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2583 definition_sqls = [ 2584 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2585 for definition in expression.args.get("define", []) 2586 ] 2587 definitions = self.expressions(sqls=definition_sqls) 2588 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2589 body = "".join( 2590 ( 2591 partition, 2592 order, 2593 measures, 2594 rows, 2595 after, 2596 pattern, 2597 define, 2598 ) 2599 ) 2600 alias = self.sql(expression, "alias") 2601 alias = f" {alias}" if alias else "" 2602 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2603 2604 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2605 limit = expression.args.get("limit") 2606 2607 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2608 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2609 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2610 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2611 2612 return csv( 2613 *sqls, 2614 *[self.sql(join) for join in expression.args.get("joins") or []], 2615 self.sql(expression, "match"), 2616 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2617 self.sql(expression, "prewhere"), 2618 self.sql(expression, "where"), 2619 self.sql(expression, "connect"), 2620 self.sql(expression, "group"), 2621 self.sql(expression, "having"), 2622 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2623 self.sql(expression, "order"), 2624 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2625 *self.after_limit_modifiers(expression), 2626 self.options_modifier(expression), 2627 self.for_modifiers(expression), 2628 sep="", 2629 ) 2630 2631 def options_modifier(self, expression: exp.Expression) -> str: 2632 options = self.expressions(expression, key="options") 2633 return f" {options}" if options else "" 2634 2635 def for_modifiers(self, expression: exp.Expression) -> str: 2636 for_modifiers = self.expressions(expression, key="for") 2637 return f"{self.sep()}FOR XML{self.seg(for_modifiers)}" if for_modifiers else "" 2638 2639 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2640 self.unsupported("Unsupported query option.") 2641 return "" 2642 2643 def offset_limit_modifiers( 2644 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2645 ) -> t.List[str]: 2646 return [ 2647 self.sql(expression, "offset") if fetch else self.sql(limit), 2648 self.sql(limit) if fetch else self.sql(expression, "offset"), 2649 ] 2650 2651 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2652 locks = self.expressions(expression, key="locks", sep=" ") 2653 locks = f" {locks}" if locks else "" 2654 return [locks, self.sql(expression, "sample")] 2655 2656 def select_sql(self, expression: exp.Select) -> str: 2657 into = expression.args.get("into") 2658 if not self.SUPPORTS_SELECT_INTO and into: 2659 into.pop() 2660 2661 hint = self.sql(expression, "hint") 2662 distinct = self.sql(expression, "distinct") 2663 distinct = f" {distinct}" if distinct else "" 2664 kind = self.sql(expression, "kind") 2665 2666 limit = expression.args.get("limit") 2667 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2668 top = self.limit_sql(limit, top=True) 2669 limit.pop() 2670 else: 2671 top = "" 2672 2673 expressions = self.expressions(expression) 2674 2675 if kind: 2676 if kind in self.SELECT_KINDS: 2677 kind = f" AS {kind}" 2678 else: 2679 if kind == "STRUCT": 2680 expressions = self.expressions( 2681 sqls=[ 2682 self.sql( 2683 exp.Struct( 2684 expressions=[ 2685 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2686 if isinstance(e, exp.Alias) 2687 else e 2688 for e in expression.expressions 2689 ] 2690 ) 2691 ) 2692 ] 2693 ) 2694 kind = "" 2695 2696 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2697 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2698 2699 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2700 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2701 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2702 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2703 sql = self.query_modifiers( 2704 expression, 2705 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2706 self.sql(expression, "into", comment=False), 2707 self.sql(expression, "from", comment=False), 2708 ) 2709 2710 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2711 if expression.args.get("with"): 2712 sql = self.maybe_comment(sql, expression) 2713 expression.pop_comments() 2714 2715 sql = self.prepend_ctes(expression, sql) 2716 2717 if not self.SUPPORTS_SELECT_INTO and into: 2718 if into.args.get("temporary"): 2719 table_kind = " TEMPORARY" 2720 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2721 table_kind = " UNLOGGED" 2722 else: 2723 table_kind = "" 2724 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2725 2726 return sql 2727 2728 def schema_sql(self, expression: exp.Schema) -> str: 2729 this = self.sql(expression, "this") 2730 sql = self.schema_columns_sql(expression) 2731 return f"{this} {sql}" if this and sql else this or sql 2732 2733 def schema_columns_sql(self, expression: exp.Schema) -> str: 2734 if expression.expressions: 2735 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2736 return "" 2737 2738 def star_sql(self, expression: exp.Star) -> str: 2739 except_ = self.expressions(expression, key="except", flat=True) 2740 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2741 replace = self.expressions(expression, key="replace", flat=True) 2742 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2743 rename = self.expressions(expression, key="rename", flat=True) 2744 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2745 return f"*{except_}{replace}{rename}" 2746 2747 def parameter_sql(self, expression: exp.Parameter) -> str: 2748 this = self.sql(expression, "this") 2749 return f"{self.PARAMETER_TOKEN}{this}" 2750 2751 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2752 this = self.sql(expression, "this") 2753 kind = expression.text("kind") 2754 if kind: 2755 kind = f"{kind}." 2756 return f"@@{kind}{this}" 2757 2758 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2759 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2760 2761 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2762 alias = self.sql(expression, "alias") 2763 alias = f"{sep}{alias}" if alias else "" 2764 sample = self.sql(expression, "sample") 2765 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2766 alias = f"{sample}{alias}" 2767 2768 # Set to None so it's not generated again by self.query_modifiers() 2769 expression.set("sample", None) 2770 2771 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2772 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2773 return self.prepend_ctes(expression, sql) 2774 2775 def qualify_sql(self, expression: exp.Qualify) -> str: 2776 this = self.indent(self.sql(expression, "this")) 2777 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2778 2779 def unnest_sql(self, expression: exp.Unnest) -> str: 2780 args = self.expressions(expression, flat=True) 2781 2782 alias = expression.args.get("alias") 2783 offset = expression.args.get("offset") 2784 2785 if self.UNNEST_WITH_ORDINALITY: 2786 if alias and isinstance(offset, exp.Expression): 2787 alias.append("columns", offset) 2788 2789 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2790 columns = alias.columns 2791 alias = self.sql(columns[0]) if columns else "" 2792 else: 2793 alias = self.sql(alias) 2794 2795 alias = f" AS {alias}" if alias else alias 2796 if self.UNNEST_WITH_ORDINALITY: 2797 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2798 else: 2799 if isinstance(offset, exp.Expression): 2800 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2801 elif offset: 2802 suffix = f"{alias} WITH OFFSET" 2803 else: 2804 suffix = alias 2805 2806 return f"UNNEST({args}){suffix}" 2807 2808 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2809 return "" 2810 2811 def where_sql(self, expression: exp.Where) -> str: 2812 this = self.indent(self.sql(expression, "this")) 2813 return f"{self.seg('WHERE')}{self.sep()}{this}" 2814 2815 def window_sql(self, expression: exp.Window) -> str: 2816 this = self.sql(expression, "this") 2817 partition = self.partition_by_sql(expression) 2818 order = expression.args.get("order") 2819 order = self.order_sql(order, flat=True) if order else "" 2820 spec = self.sql(expression, "spec") 2821 alias = self.sql(expression, "alias") 2822 over = self.sql(expression, "over") or "OVER" 2823 2824 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2825 2826 first = expression.args.get("first") 2827 if first is None: 2828 first = "" 2829 else: 2830 first = "FIRST" if first else "LAST" 2831 2832 if not partition and not order and not spec and alias: 2833 return f"{this} {alias}" 2834 2835 args = self.format_args( 2836 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2837 ) 2838 return f"{this} ({args})" 2839 2840 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2841 partition = self.expressions(expression, key="partition_by", flat=True) 2842 return f"PARTITION BY {partition}" if partition else "" 2843 2844 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2845 kind = self.sql(expression, "kind") 2846 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2847 end = ( 2848 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2849 or "CURRENT ROW" 2850 ) 2851 2852 window_spec = f"{kind} BETWEEN {start} AND {end}" 2853 2854 exclude = self.sql(expression, "exclude") 2855 if exclude: 2856 if self.SUPPORTS_WINDOW_EXCLUDE: 2857 window_spec += f" EXCLUDE {exclude}" 2858 else: 2859 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2860 2861 return window_spec 2862 2863 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2864 this = self.sql(expression, "this") 2865 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2866 return f"{this} WITHIN GROUP ({expression_sql})" 2867 2868 def between_sql(self, expression: exp.Between) -> str: 2869 this = self.sql(expression, "this") 2870 low = self.sql(expression, "low") 2871 high = self.sql(expression, "high") 2872 symmetric = expression.args.get("symmetric") 2873 2874 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2875 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2876 2877 flag = ( 2878 " SYMMETRIC" 2879 if symmetric 2880 else " ASYMMETRIC" 2881 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2882 else "" # silently drop ASYMMETRIC – semantics identical 2883 ) 2884 return f"{this} BETWEEN{flag} {low} AND {high}" 2885 2886 def bracket_offset_expressions( 2887 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2888 ) -> t.List[exp.Expression]: 2889 return apply_index_offset( 2890 expression.this, 2891 expression.expressions, 2892 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2893 dialect=self.dialect, 2894 ) 2895 2896 def bracket_sql(self, expression: exp.Bracket) -> str: 2897 expressions = self.bracket_offset_expressions(expression) 2898 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2899 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2900 2901 def all_sql(self, expression: exp.All) -> str: 2902 return f"ALL {self.wrap(expression)}" 2903 2904 def any_sql(self, expression: exp.Any) -> str: 2905 this = self.sql(expression, "this") 2906 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2907 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2908 this = self.wrap(this) 2909 return f"ANY{this}" 2910 return f"ANY {this}" 2911 2912 def exists_sql(self, expression: exp.Exists) -> str: 2913 return f"EXISTS{self.wrap(expression)}" 2914 2915 def case_sql(self, expression: exp.Case) -> str: 2916 this = self.sql(expression, "this") 2917 statements = [f"CASE {this}" if this else "CASE"] 2918 2919 for e in expression.args["ifs"]: 2920 statements.append(f"WHEN {self.sql(e, 'this')}") 2921 statements.append(f"THEN {self.sql(e, 'true')}") 2922 2923 default = self.sql(expression, "default") 2924 2925 if default: 2926 statements.append(f"ELSE {default}") 2927 2928 statements.append("END") 2929 2930 if self.pretty and self.too_wide(statements): 2931 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2932 2933 return " ".join(statements) 2934 2935 def constraint_sql(self, expression: exp.Constraint) -> str: 2936 this = self.sql(expression, "this") 2937 expressions = self.expressions(expression, flat=True) 2938 return f"CONSTRAINT {this} {expressions}" 2939 2940 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2941 order = expression.args.get("order") 2942 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2943 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2944 2945 def extract_sql(self, expression: exp.Extract) -> str: 2946 from sqlglot.dialects.dialect import map_date_part 2947 2948 this = ( 2949 map_date_part(expression.this, self.dialect) 2950 if self.NORMALIZE_EXTRACT_DATE_PARTS 2951 else expression.this 2952 ) 2953 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2954 expression_sql = self.sql(expression, "expression") 2955 2956 return f"EXTRACT({this_sql} FROM {expression_sql})" 2957 2958 def trim_sql(self, expression: exp.Trim) -> str: 2959 trim_type = self.sql(expression, "position") 2960 2961 if trim_type == "LEADING": 2962 func_name = "LTRIM" 2963 elif trim_type == "TRAILING": 2964 func_name = "RTRIM" 2965 else: 2966 func_name = "TRIM" 2967 2968 return self.func(func_name, expression.this, expression.expression) 2969 2970 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2971 args = expression.expressions 2972 if isinstance(expression, exp.ConcatWs): 2973 args = args[1:] # Skip the delimiter 2974 2975 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2976 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2977 2978 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2979 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2980 2981 return args 2982 2983 def concat_sql(self, expression: exp.Concat) -> str: 2984 expressions = self.convert_concat_args(expression) 2985 2986 # Some dialects don't allow a single-argument CONCAT call 2987 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2988 return self.sql(expressions[0]) 2989 2990 return self.func("CONCAT", *expressions) 2991 2992 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2993 return self.func( 2994 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2995 ) 2996 2997 def check_sql(self, expression: exp.Check) -> str: 2998 this = self.sql(expression, key="this") 2999 return f"CHECK ({this})" 3000 3001 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3002 expressions = self.expressions(expression, flat=True) 3003 expressions = f" ({expressions})" if expressions else "" 3004 reference = self.sql(expression, "reference") 3005 reference = f" {reference}" if reference else "" 3006 delete = self.sql(expression, "delete") 3007 delete = f" ON DELETE {delete}" if delete else "" 3008 update = self.sql(expression, "update") 3009 update = f" ON UPDATE {update}" if update else "" 3010 options = self.expressions(expression, key="options", flat=True, sep=" ") 3011 options = f" {options}" if options else "" 3012 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 3013 3014 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3015 expressions = self.expressions(expression, flat=True) 3016 include = self.sql(expression, "include") 3017 options = self.expressions(expression, key="options", flat=True, sep=" ") 3018 options = f" {options}" if options else "" 3019 return f"PRIMARY KEY ({expressions}){include}{options}" 3020 3021 def if_sql(self, expression: exp.If) -> str: 3022 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 3023 3024 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 3025 modifier = expression.args.get("modifier") 3026 modifier = f" {modifier}" if modifier else "" 3027 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 3028 3029 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 3030 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 3031 3032 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3033 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3034 3035 if expression.args.get("escape"): 3036 path = self.escape_str(path) 3037 3038 if self.QUOTE_JSON_PATH: 3039 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3040 3041 return path 3042 3043 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3044 if isinstance(expression, exp.JSONPathPart): 3045 transform = self.TRANSFORMS.get(expression.__class__) 3046 if not callable(transform): 3047 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3048 return "" 3049 3050 return transform(self, expression) 3051 3052 if isinstance(expression, int): 3053 return str(expression) 3054 3055 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3056 escaped = expression.replace("'", "\\'") 3057 escaped = f"\\'{expression}\\'" 3058 else: 3059 escaped = expression.replace('"', '\\"') 3060 escaped = f'"{escaped}"' 3061 3062 return escaped 3063 3064 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3065 return f"{self.sql(expression, 'this')} FORMAT JSON" 3066 3067 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3068 # Output the Teradata column FORMAT override. 3069 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3070 this = self.sql(expression, "this") 3071 fmt = self.sql(expression, "format") 3072 return f"{this} (FORMAT {fmt})" 3073 3074 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3075 null_handling = expression.args.get("null_handling") 3076 null_handling = f" {null_handling}" if null_handling else "" 3077 3078 unique_keys = expression.args.get("unique_keys") 3079 if unique_keys is not None: 3080 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3081 else: 3082 unique_keys = "" 3083 3084 return_type = self.sql(expression, "return_type") 3085 return_type = f" RETURNING {return_type}" if return_type else "" 3086 encoding = self.sql(expression, "encoding") 3087 encoding = f" ENCODING {encoding}" if encoding else "" 3088 3089 return self.func( 3090 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3091 *expression.expressions, 3092 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3093 ) 3094 3095 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3096 return self.jsonobject_sql(expression) 3097 3098 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3099 null_handling = expression.args.get("null_handling") 3100 null_handling = f" {null_handling}" if null_handling else "" 3101 return_type = self.sql(expression, "return_type") 3102 return_type = f" RETURNING {return_type}" if return_type else "" 3103 strict = " STRICT" if expression.args.get("strict") else "" 3104 return self.func( 3105 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3106 ) 3107 3108 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3109 this = self.sql(expression, "this") 3110 order = self.sql(expression, "order") 3111 null_handling = expression.args.get("null_handling") 3112 null_handling = f" {null_handling}" if null_handling else "" 3113 return_type = self.sql(expression, "return_type") 3114 return_type = f" RETURNING {return_type}" if return_type else "" 3115 strict = " STRICT" if expression.args.get("strict") else "" 3116 return self.func( 3117 "JSON_ARRAYAGG", 3118 this, 3119 suffix=f"{order}{null_handling}{return_type}{strict})", 3120 ) 3121 3122 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3123 path = self.sql(expression, "path") 3124 path = f" PATH {path}" if path else "" 3125 nested_schema = self.sql(expression, "nested_schema") 3126 3127 if nested_schema: 3128 return f"NESTED{path} {nested_schema}" 3129 3130 this = self.sql(expression, "this") 3131 kind = self.sql(expression, "kind") 3132 kind = f" {kind}" if kind else "" 3133 return f"{this}{kind}{path}" 3134 3135 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3136 return self.func("COLUMNS", *expression.expressions) 3137 3138 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3139 this = self.sql(expression, "this") 3140 path = self.sql(expression, "path") 3141 path = f", {path}" if path else "" 3142 error_handling = expression.args.get("error_handling") 3143 error_handling = f" {error_handling}" if error_handling else "" 3144 empty_handling = expression.args.get("empty_handling") 3145 empty_handling = f" {empty_handling}" if empty_handling else "" 3146 schema = self.sql(expression, "schema") 3147 return self.func( 3148 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3149 ) 3150 3151 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3152 this = self.sql(expression, "this") 3153 kind = self.sql(expression, "kind") 3154 path = self.sql(expression, "path") 3155 path = f" {path}" if path else "" 3156 as_json = " AS JSON" if expression.args.get("as_json") else "" 3157 return f"{this} {kind}{path}{as_json}" 3158 3159 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3160 this = self.sql(expression, "this") 3161 path = self.sql(expression, "path") 3162 path = f", {path}" if path else "" 3163 expressions = self.expressions(expression) 3164 with_ = ( 3165 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3166 if expressions 3167 else "" 3168 ) 3169 return f"OPENJSON({this}{path}){with_}" 3170 3171 def in_sql(self, expression: exp.In) -> str: 3172 query = expression.args.get("query") 3173 unnest = expression.args.get("unnest") 3174 field = expression.args.get("field") 3175 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3176 3177 if query: 3178 in_sql = self.sql(query) 3179 elif unnest: 3180 in_sql = self.in_unnest_op(unnest) 3181 elif field: 3182 in_sql = self.sql(field) 3183 else: 3184 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3185 3186 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3187 3188 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3189 return f"(SELECT {self.sql(unnest)})" 3190 3191 def interval_sql(self, expression: exp.Interval) -> str: 3192 unit = self.sql(expression, "unit") 3193 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3194 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3195 unit = f" {unit}" if unit else "" 3196 3197 if self.SINGLE_STRING_INTERVAL: 3198 this = expression.this.name if expression.this else "" 3199 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3200 3201 this = self.sql(expression, "this") 3202 if this: 3203 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3204 this = f" {this}" if unwrapped else f" ({this})" 3205 3206 return f"INTERVAL{this}{unit}" 3207 3208 def return_sql(self, expression: exp.Return) -> str: 3209 return f"RETURN {self.sql(expression, 'this')}" 3210 3211 def reference_sql(self, expression: exp.Reference) -> str: 3212 this = self.sql(expression, "this") 3213 expressions = self.expressions(expression, flat=True) 3214 expressions = f"({expressions})" if expressions else "" 3215 options = self.expressions(expression, key="options", flat=True, sep=" ") 3216 options = f" {options}" if options else "" 3217 return f"REFERENCES {this}{expressions}{options}" 3218 3219 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3220 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3221 parent = expression.parent 3222 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3223 return self.func( 3224 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3225 ) 3226 3227 def paren_sql(self, expression: exp.Paren) -> str: 3228 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3229 return f"({sql}{self.seg(')', sep='')}" 3230 3231 def neg_sql(self, expression: exp.Neg) -> str: 3232 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3233 this_sql = self.sql(expression, "this") 3234 sep = " " if this_sql[0] == "-" else "" 3235 return f"-{sep}{this_sql}" 3236 3237 def not_sql(self, expression: exp.Not) -> str: 3238 return f"NOT {self.sql(expression, 'this')}" 3239 3240 def alias_sql(self, expression: exp.Alias) -> str: 3241 alias = self.sql(expression, "alias") 3242 alias = f" AS {alias}" if alias else "" 3243 return f"{self.sql(expression, 'this')}{alias}" 3244 3245 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3246 alias = expression.args["alias"] 3247 3248 parent = expression.parent 3249 pivot = parent and parent.parent 3250 3251 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3252 identifier_alias = isinstance(alias, exp.Identifier) 3253 literal_alias = isinstance(alias, exp.Literal) 3254 3255 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3256 alias.replace(exp.Literal.string(alias.output_name)) 3257 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3258 alias.replace(exp.to_identifier(alias.output_name)) 3259 3260 return self.alias_sql(expression) 3261 3262 def aliases_sql(self, expression: exp.Aliases) -> str: 3263 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3264 3265 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3266 this = self.sql(expression, "this") 3267 index = self.sql(expression, "expression") 3268 return f"{this} AT {index}" 3269 3270 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3271 this = self.sql(expression, "this") 3272 zone = self.sql(expression, "zone") 3273 return f"{this} AT TIME ZONE {zone}" 3274 3275 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3276 this = self.sql(expression, "this") 3277 zone = self.sql(expression, "zone") 3278 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3279 3280 def add_sql(self, expression: exp.Add) -> str: 3281 return self.binary(expression, "+") 3282 3283 def and_sql( 3284 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3285 ) -> str: 3286 return self.connector_sql(expression, "AND", stack) 3287 3288 def or_sql( 3289 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3290 ) -> str: 3291 return self.connector_sql(expression, "OR", stack) 3292 3293 def xor_sql( 3294 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3295 ) -> str: 3296 return self.connector_sql(expression, "XOR", stack) 3297 3298 def connector_sql( 3299 self, 3300 expression: exp.Connector, 3301 op: str, 3302 stack: t.Optional[t.List[str | exp.Expression]] = None, 3303 ) -> str: 3304 if stack is not None: 3305 if expression.expressions: 3306 stack.append(self.expressions(expression, sep=f" {op} ")) 3307 else: 3308 stack.append(expression.right) 3309 if expression.comments and self.comments: 3310 for comment in expression.comments: 3311 if comment: 3312 op += f" /*{self.sanitize_comment(comment)}*/" 3313 stack.extend((op, expression.left)) 3314 return op 3315 3316 stack = [expression] 3317 sqls: t.List[str] = [] 3318 ops = set() 3319 3320 while stack: 3321 node = stack.pop() 3322 if isinstance(node, exp.Connector): 3323 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3324 else: 3325 sql = self.sql(node) 3326 if sqls and sqls[-1] in ops: 3327 sqls[-1] += f" {sql}" 3328 else: 3329 sqls.append(sql) 3330 3331 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3332 return sep.join(sqls) 3333 3334 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3335 return self.binary(expression, "&") 3336 3337 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3338 return self.binary(expression, "<<") 3339 3340 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3341 return f"~{self.sql(expression, 'this')}" 3342 3343 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3344 return self.binary(expression, "|") 3345 3346 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3347 return self.binary(expression, ">>") 3348 3349 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3350 return self.binary(expression, "^") 3351 3352 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3353 format_sql = self.sql(expression, "format") 3354 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3355 to_sql = self.sql(expression, "to") 3356 to_sql = f" {to_sql}" if to_sql else "" 3357 action = self.sql(expression, "action") 3358 action = f" {action}" if action else "" 3359 default = self.sql(expression, "default") 3360 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3361 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3362 3363 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3364 zone = self.sql(expression, "this") 3365 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3366 3367 def collate_sql(self, expression: exp.Collate) -> str: 3368 if self.COLLATE_IS_FUNC: 3369 return self.function_fallback_sql(expression) 3370 return self.binary(expression, "COLLATE") 3371 3372 def command_sql(self, expression: exp.Command) -> str: 3373 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3374 3375 def comment_sql(self, expression: exp.Comment) -> str: 3376 this = self.sql(expression, "this") 3377 kind = expression.args["kind"] 3378 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3379 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3380 expression_sql = self.sql(expression, "expression") 3381 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3382 3383 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3384 this = self.sql(expression, "this") 3385 delete = " DELETE" if expression.args.get("delete") else "" 3386 recompress = self.sql(expression, "recompress") 3387 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3388 to_disk = self.sql(expression, "to_disk") 3389 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3390 to_volume = self.sql(expression, "to_volume") 3391 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3392 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3393 3394 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3395 where = self.sql(expression, "where") 3396 group = self.sql(expression, "group") 3397 aggregates = self.expressions(expression, key="aggregates") 3398 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3399 3400 if not (where or group or aggregates) and len(expression.expressions) == 1: 3401 return f"TTL {self.expressions(expression, flat=True)}" 3402 3403 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3404 3405 def transaction_sql(self, expression: exp.Transaction) -> str: 3406 return "BEGIN" 3407 3408 def commit_sql(self, expression: exp.Commit) -> str: 3409 chain = expression.args.get("chain") 3410 if chain is not None: 3411 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3412 3413 return f"COMMIT{chain or ''}" 3414 3415 def rollback_sql(self, expression: exp.Rollback) -> str: 3416 savepoint = expression.args.get("savepoint") 3417 savepoint = f" TO {savepoint}" if savepoint else "" 3418 return f"ROLLBACK{savepoint}" 3419 3420 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3421 this = self.sql(expression, "this") 3422 3423 dtype = self.sql(expression, "dtype") 3424 if dtype: 3425 collate = self.sql(expression, "collate") 3426 collate = f" COLLATE {collate}" if collate else "" 3427 using = self.sql(expression, "using") 3428 using = f" USING {using}" if using else "" 3429 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3430 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3431 3432 default = self.sql(expression, "default") 3433 if default: 3434 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3435 3436 comment = self.sql(expression, "comment") 3437 if comment: 3438 return f"ALTER COLUMN {this} COMMENT {comment}" 3439 3440 visible = expression.args.get("visible") 3441 if visible: 3442 return f"ALTER COLUMN {this} SET {visible}" 3443 3444 allow_null = expression.args.get("allow_null") 3445 drop = expression.args.get("drop") 3446 3447 if not drop and not allow_null: 3448 self.unsupported("Unsupported ALTER COLUMN syntax") 3449 3450 if allow_null is not None: 3451 keyword = "DROP" if drop else "SET" 3452 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3453 3454 return f"ALTER COLUMN {this} DROP DEFAULT" 3455 3456 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3457 this = self.sql(expression, "this") 3458 3459 visible = expression.args.get("visible") 3460 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3461 3462 return f"ALTER INDEX {this} {visible_sql}" 3463 3464 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3465 this = self.sql(expression, "this") 3466 if not isinstance(expression.this, exp.Var): 3467 this = f"KEY DISTKEY {this}" 3468 return f"ALTER DISTSTYLE {this}" 3469 3470 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3471 compound = " COMPOUND" if expression.args.get("compound") else "" 3472 this = self.sql(expression, "this") 3473 expressions = self.expressions(expression, flat=True) 3474 expressions = f"({expressions})" if expressions else "" 3475 return f"ALTER{compound} SORTKEY {this or expressions}" 3476 3477 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3478 if not self.RENAME_TABLE_WITH_DB: 3479 # Remove db from tables 3480 expression = expression.transform( 3481 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3482 ).assert_is(exp.AlterRename) 3483 this = self.sql(expression, "this") 3484 return f"RENAME TO {this}" 3485 3486 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3487 exists = " IF EXISTS" if expression.args.get("exists") else "" 3488 old_column = self.sql(expression, "this") 3489 new_column = self.sql(expression, "to") 3490 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3491 3492 def alterset_sql(self, expression: exp.AlterSet) -> str: 3493 exprs = self.expressions(expression, flat=True) 3494 if self.ALTER_SET_WRAPPED: 3495 exprs = f"({exprs})" 3496 3497 return f"SET {exprs}" 3498 3499 def alter_sql(self, expression: exp.Alter) -> str: 3500 actions = expression.args["actions"] 3501 3502 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3503 actions[0], exp.ColumnDef 3504 ): 3505 actions_sql = self.expressions(expression, key="actions", flat=True) 3506 actions_sql = f"ADD {actions_sql}" 3507 else: 3508 actions_list = [] 3509 for action in actions: 3510 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3511 action_sql = self.add_column_sql(action) 3512 else: 3513 action_sql = self.sql(action) 3514 if isinstance(action, exp.Query): 3515 action_sql = f"AS {action_sql}" 3516 3517 actions_list.append(action_sql) 3518 3519 actions_sql = self.format_args(*actions_list).lstrip("\n") 3520 3521 exists = " IF EXISTS" if expression.args.get("exists") else "" 3522 on_cluster = self.sql(expression, "cluster") 3523 on_cluster = f" {on_cluster}" if on_cluster else "" 3524 only = " ONLY" if expression.args.get("only") else "" 3525 options = self.expressions(expression, key="options") 3526 options = f", {options}" if options else "" 3527 kind = self.sql(expression, "kind") 3528 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3529 3530 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}" 3531 3532 def add_column_sql(self, expression: exp.Expression) -> str: 3533 sql = self.sql(expression) 3534 if isinstance(expression, exp.Schema): 3535 column_text = " COLUMNS" 3536 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3537 column_text = " COLUMN" 3538 else: 3539 column_text = "" 3540 3541 return f"ADD{column_text} {sql}" 3542 3543 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3544 expressions = self.expressions(expression) 3545 exists = " IF EXISTS " if expression.args.get("exists") else " " 3546 return f"DROP{exists}{expressions}" 3547 3548 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3549 return f"ADD {self.expressions(expression, indent=False)}" 3550 3551 def addpartition_sql(self, expression: exp.AddPartition) -> str: 3552 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 3553 return f"ADD {exists}{self.sql(expression.this)}" 3554 3555 def distinct_sql(self, expression: exp.Distinct) -> str: 3556 this = self.expressions(expression, flat=True) 3557 3558 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3559 case = exp.case() 3560 for arg in expression.expressions: 3561 case = case.when(arg.is_(exp.null()), exp.null()) 3562 this = self.sql(case.else_(f"({this})")) 3563 3564 this = f" {this}" if this else "" 3565 3566 on = self.sql(expression, "on") 3567 on = f" ON {on}" if on else "" 3568 return f"DISTINCT{this}{on}" 3569 3570 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3571 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3572 3573 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3574 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3575 3576 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3577 this_sql = self.sql(expression, "this") 3578 expression_sql = self.sql(expression, "expression") 3579 kind = "MAX" if expression.args.get("max") else "MIN" 3580 return f"{this_sql} HAVING {kind} {expression_sql}" 3581 3582 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3583 return self.sql( 3584 exp.Cast( 3585 this=exp.Div(this=expression.this, expression=expression.expression), 3586 to=exp.DataType(this=exp.DataType.Type.INT), 3587 ) 3588 ) 3589 3590 def dpipe_sql(self, expression: exp.DPipe) -> str: 3591 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3592 return self.func( 3593 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3594 ) 3595 return self.binary(expression, "||") 3596 3597 def div_sql(self, expression: exp.Div) -> str: 3598 l, r = expression.left, expression.right 3599 3600 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3601 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3602 3603 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3604 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3605 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3606 3607 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3608 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3609 return self.sql( 3610 exp.cast( 3611 l / r, 3612 to=exp.DataType.Type.BIGINT, 3613 ) 3614 ) 3615 3616 return self.binary(expression, "/") 3617 3618 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3619 n = exp._wrap(expression.this, exp.Binary) 3620 d = exp._wrap(expression.expression, exp.Binary) 3621 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3622 3623 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3624 return self.binary(expression, "OVERLAPS") 3625 3626 def distance_sql(self, expression: exp.Distance) -> str: 3627 return self.binary(expression, "<->") 3628 3629 def dot_sql(self, expression: exp.Dot) -> str: 3630 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3631 3632 def eq_sql(self, expression: exp.EQ) -> str: 3633 return self.binary(expression, "=") 3634 3635 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3636 return self.binary(expression, ":=") 3637 3638 def escape_sql(self, expression: exp.Escape) -> str: 3639 return self.binary(expression, "ESCAPE") 3640 3641 def glob_sql(self, expression: exp.Glob) -> str: 3642 return self.binary(expression, "GLOB") 3643 3644 def gt_sql(self, expression: exp.GT) -> str: 3645 return self.binary(expression, ">") 3646 3647 def gte_sql(self, expression: exp.GTE) -> str: 3648 return self.binary(expression, ">=") 3649 3650 def ilike_sql(self, expression: exp.ILike) -> str: 3651 return self.binary(expression, "ILIKE") 3652 3653 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3654 return self.binary(expression, "ILIKE ANY") 3655 3656 def is_sql(self, expression: exp.Is) -> str: 3657 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3658 return self.sql( 3659 expression.this if expression.expression.this else exp.not_(expression.this) 3660 ) 3661 return self.binary(expression, "IS") 3662 3663 def like_sql(self, expression: exp.Like) -> str: 3664 return self.binary(expression, "LIKE") 3665 3666 def likeany_sql(self, expression: exp.LikeAny) -> str: 3667 return self.binary(expression, "LIKE ANY") 3668 3669 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3670 return self.binary(expression, "SIMILAR TO") 3671 3672 def lt_sql(self, expression: exp.LT) -> str: 3673 return self.binary(expression, "<") 3674 3675 def lte_sql(self, expression: exp.LTE) -> str: 3676 return self.binary(expression, "<=") 3677 3678 def mod_sql(self, expression: exp.Mod) -> str: 3679 return self.binary(expression, "%") 3680 3681 def mul_sql(self, expression: exp.Mul) -> str: 3682 return self.binary(expression, "*") 3683 3684 def neq_sql(self, expression: exp.NEQ) -> str: 3685 return self.binary(expression, "<>") 3686 3687 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3688 return self.binary(expression, "IS NOT DISTINCT FROM") 3689 3690 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3691 return self.binary(expression, "IS DISTINCT FROM") 3692 3693 def slice_sql(self, expression: exp.Slice) -> str: 3694 return self.binary(expression, ":") 3695 3696 def sub_sql(self, expression: exp.Sub) -> str: 3697 return self.binary(expression, "-") 3698 3699 def trycast_sql(self, expression: exp.TryCast) -> str: 3700 return self.cast_sql(expression, safe_prefix="TRY_") 3701 3702 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3703 return self.cast_sql(expression) 3704 3705 def try_sql(self, expression: exp.Try) -> str: 3706 if not self.TRY_SUPPORTED: 3707 self.unsupported("Unsupported TRY function") 3708 return self.sql(expression, "this") 3709 3710 return self.func("TRY", expression.this) 3711 3712 def log_sql(self, expression: exp.Log) -> str: 3713 this = expression.this 3714 expr = expression.expression 3715 3716 if self.dialect.LOG_BASE_FIRST is False: 3717 this, expr = expr, this 3718 elif self.dialect.LOG_BASE_FIRST is None and expr: 3719 if this.name in ("2", "10"): 3720 return self.func(f"LOG{this.name}", expr) 3721 3722 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3723 3724 return self.func("LOG", this, expr) 3725 3726 def use_sql(self, expression: exp.Use) -> str: 3727 kind = self.sql(expression, "kind") 3728 kind = f" {kind}" if kind else "" 3729 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3730 this = f" {this}" if this else "" 3731 return f"USE{kind}{this}" 3732 3733 def binary(self, expression: exp.Binary, op: str) -> str: 3734 sqls: t.List[str] = [] 3735 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3736 binary_type = type(expression) 3737 3738 while stack: 3739 node = stack.pop() 3740 3741 if type(node) is binary_type: 3742 op_func = node.args.get("operator") 3743 if op_func: 3744 op = f"OPERATOR({self.sql(op_func)})" 3745 3746 stack.append(node.right) 3747 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3748 stack.append(node.left) 3749 else: 3750 sqls.append(self.sql(node)) 3751 3752 return "".join(sqls) 3753 3754 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3755 to_clause = self.sql(expression, "to") 3756 if to_clause: 3757 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3758 3759 return self.function_fallback_sql(expression) 3760 3761 def function_fallback_sql(self, expression: exp.Func) -> str: 3762 args = [] 3763 3764 for key in expression.arg_types: 3765 arg_value = expression.args.get(key) 3766 3767 if isinstance(arg_value, list): 3768 for value in arg_value: 3769 args.append(value) 3770 elif arg_value is not None: 3771 args.append(arg_value) 3772 3773 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3774 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3775 else: 3776 name = expression.sql_name() 3777 3778 return self.func(name, *args) 3779 3780 def func( 3781 self, 3782 name: str, 3783 *args: t.Optional[exp.Expression | str], 3784 prefix: str = "(", 3785 suffix: str = ")", 3786 normalize: bool = True, 3787 ) -> str: 3788 name = self.normalize_func(name) if normalize else name 3789 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3790 3791 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3792 arg_sqls = tuple( 3793 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3794 ) 3795 if self.pretty and self.too_wide(arg_sqls): 3796 return self.indent( 3797 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3798 ) 3799 return sep.join(arg_sqls) 3800 3801 def too_wide(self, args: t.Iterable) -> bool: 3802 return sum(len(arg) for arg in args) > self.max_text_width 3803 3804 def format_time( 3805 self, 3806 expression: exp.Expression, 3807 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3808 inverse_time_trie: t.Optional[t.Dict] = None, 3809 ) -> t.Optional[str]: 3810 return format_time( 3811 self.sql(expression, "format"), 3812 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3813 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3814 ) 3815 3816 def expressions( 3817 self, 3818 expression: t.Optional[exp.Expression] = None, 3819 key: t.Optional[str] = None, 3820 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3821 flat: bool = False, 3822 indent: bool = True, 3823 skip_first: bool = False, 3824 skip_last: bool = False, 3825 sep: str = ", ", 3826 prefix: str = "", 3827 dynamic: bool = False, 3828 new_line: bool = False, 3829 ) -> str: 3830 expressions = expression.args.get(key or "expressions") if expression else sqls 3831 3832 if not expressions: 3833 return "" 3834 3835 if flat: 3836 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3837 3838 num_sqls = len(expressions) 3839 result_sqls = [] 3840 3841 for i, e in enumerate(expressions): 3842 sql = self.sql(e, comment=False) 3843 if not sql: 3844 continue 3845 3846 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3847 3848 if self.pretty: 3849 if self.leading_comma: 3850 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3851 else: 3852 result_sqls.append( 3853 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3854 ) 3855 else: 3856 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3857 3858 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3859 if new_line: 3860 result_sqls.insert(0, "") 3861 result_sqls.append("") 3862 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3863 else: 3864 result_sql = "".join(result_sqls) 3865 3866 return ( 3867 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3868 if indent 3869 else result_sql 3870 ) 3871 3872 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3873 flat = flat or isinstance(expression.parent, exp.Properties) 3874 expressions_sql = self.expressions(expression, flat=flat) 3875 if flat: 3876 return f"{op} {expressions_sql}" 3877 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3878 3879 def naked_property(self, expression: exp.Property) -> str: 3880 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3881 if not property_name: 3882 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3883 return f"{property_name} {self.sql(expression, 'this')}" 3884 3885 def tag_sql(self, expression: exp.Tag) -> str: 3886 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3887 3888 def token_sql(self, token_type: TokenType) -> str: 3889 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3890 3891 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3892 this = self.sql(expression, "this") 3893 expressions = self.no_identify(self.expressions, expression) 3894 expressions = ( 3895 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3896 ) 3897 return f"{this}{expressions}" if expressions.strip() != "" else this 3898 3899 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3900 this = self.sql(expression, "this") 3901 expressions = self.expressions(expression, flat=True) 3902 return f"{this}({expressions})" 3903 3904 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3905 return self.binary(expression, "=>") 3906 3907 def when_sql(self, expression: exp.When) -> str: 3908 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3909 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3910 condition = self.sql(expression, "condition") 3911 condition = f" AND {condition}" if condition else "" 3912 3913 then_expression = expression.args.get("then") 3914 if isinstance(then_expression, exp.Insert): 3915 this = self.sql(then_expression, "this") 3916 this = f"INSERT {this}" if this else "INSERT" 3917 then = self.sql(then_expression, "expression") 3918 then = f"{this} VALUES {then}" if then else this 3919 elif isinstance(then_expression, exp.Update): 3920 if isinstance(then_expression.args.get("expressions"), exp.Star): 3921 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3922 else: 3923 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3924 else: 3925 then = self.sql(then_expression) 3926 return f"WHEN {matched}{source}{condition} THEN {then}" 3927 3928 def whens_sql(self, expression: exp.Whens) -> str: 3929 return self.expressions(expression, sep=" ", indent=False) 3930 3931 def merge_sql(self, expression: exp.Merge) -> str: 3932 table = expression.this 3933 table_alias = "" 3934 3935 hints = table.args.get("hints") 3936 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3937 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3938 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3939 3940 this = self.sql(table) 3941 using = f"USING {self.sql(expression, 'using')}" 3942 on = f"ON {self.sql(expression, 'on')}" 3943 whens = self.sql(expression, "whens") 3944 3945 returning = self.sql(expression, "returning") 3946 if returning: 3947 whens = f"{whens}{returning}" 3948 3949 sep = self.sep() 3950 3951 return self.prepend_ctes( 3952 expression, 3953 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3954 ) 3955 3956 @unsupported_args("format") 3957 def tochar_sql(self, expression: exp.ToChar) -> str: 3958 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3959 3960 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3961 if not self.SUPPORTS_TO_NUMBER: 3962 self.unsupported("Unsupported TO_NUMBER function") 3963 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3964 3965 fmt = expression.args.get("format") 3966 if not fmt: 3967 self.unsupported("Conversion format is required for TO_NUMBER") 3968 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3969 3970 return self.func("TO_NUMBER", expression.this, fmt) 3971 3972 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3973 this = self.sql(expression, "this") 3974 kind = self.sql(expression, "kind") 3975 settings_sql = self.expressions(expression, key="settings", sep=" ") 3976 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3977 return f"{this}({kind}{args})" 3978 3979 def dictrange_sql(self, expression: exp.DictRange) -> str: 3980 this = self.sql(expression, "this") 3981 max = self.sql(expression, "max") 3982 min = self.sql(expression, "min") 3983 return f"{this}(MIN {min} MAX {max})" 3984 3985 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3986 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3987 3988 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3989 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3990 3991 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3992 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3993 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3994 3995 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3996 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3997 expressions = self.expressions(expression, flat=True) 3998 expressions = f" {self.wrap(expressions)}" if expressions else "" 3999 buckets = self.sql(expression, "buckets") 4000 kind = self.sql(expression, "kind") 4001 buckets = f" BUCKETS {buckets}" if buckets else "" 4002 order = self.sql(expression, "order") 4003 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 4004 4005 def oncluster_sql(self, expression: exp.OnCluster) -> str: 4006 return "" 4007 4008 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4009 expressions = self.expressions(expression, key="expressions", flat=True) 4010 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4011 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4012 buckets = self.sql(expression, "buckets") 4013 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 4014 4015 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4016 this = self.sql(expression, "this") 4017 having = self.sql(expression, "having") 4018 4019 if having: 4020 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4021 4022 return self.func("ANY_VALUE", this) 4023 4024 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4025 transform = self.func("TRANSFORM", *expression.expressions) 4026 row_format_before = self.sql(expression, "row_format_before") 4027 row_format_before = f" {row_format_before}" if row_format_before else "" 4028 record_writer = self.sql(expression, "record_writer") 4029 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4030 using = f" USING {self.sql(expression, 'command_script')}" 4031 schema = self.sql(expression, "schema") 4032 schema = f" AS {schema}" if schema else "" 4033 row_format_after = self.sql(expression, "row_format_after") 4034 row_format_after = f" {row_format_after}" if row_format_after else "" 4035 record_reader = self.sql(expression, "record_reader") 4036 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4037 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 4038 4039 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4040 key_block_size = self.sql(expression, "key_block_size") 4041 if key_block_size: 4042 return f"KEY_BLOCK_SIZE = {key_block_size}" 4043 4044 using = self.sql(expression, "using") 4045 if using: 4046 return f"USING {using}" 4047 4048 parser = self.sql(expression, "parser") 4049 if parser: 4050 return f"WITH PARSER {parser}" 4051 4052 comment = self.sql(expression, "comment") 4053 if comment: 4054 return f"COMMENT {comment}" 4055 4056 visible = expression.args.get("visible") 4057 if visible is not None: 4058 return "VISIBLE" if visible else "INVISIBLE" 4059 4060 engine_attr = self.sql(expression, "engine_attr") 4061 if engine_attr: 4062 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4063 4064 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4065 if secondary_engine_attr: 4066 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4067 4068 self.unsupported("Unsupported index constraint option.") 4069 return "" 4070 4071 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 4072 enforced = " ENFORCED" if expression.args.get("enforced") else "" 4073 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 4074 4075 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4076 kind = self.sql(expression, "kind") 4077 kind = f"{kind} INDEX" if kind else "INDEX" 4078 this = self.sql(expression, "this") 4079 this = f" {this}" if this else "" 4080 index_type = self.sql(expression, "index_type") 4081 index_type = f" USING {index_type}" if index_type else "" 4082 expressions = self.expressions(expression, flat=True) 4083 expressions = f" ({expressions})" if expressions else "" 4084 options = self.expressions(expression, key="options", sep=" ") 4085 options = f" {options}" if options else "" 4086 return f"{kind}{this}{index_type}{expressions}{options}" 4087 4088 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4089 if self.NVL2_SUPPORTED: 4090 return self.function_fallback_sql(expression) 4091 4092 case = exp.Case().when( 4093 expression.this.is_(exp.null()).not_(copy=False), 4094 expression.args["true"], 4095 copy=False, 4096 ) 4097 else_cond = expression.args.get("false") 4098 if else_cond: 4099 case.else_(else_cond, copy=False) 4100 4101 return self.sql(case) 4102 4103 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4104 this = self.sql(expression, "this") 4105 expr = self.sql(expression, "expression") 4106 iterator = self.sql(expression, "iterator") 4107 condition = self.sql(expression, "condition") 4108 condition = f" IF {condition}" if condition else "" 4109 return f"{this} FOR {expr} IN {iterator}{condition}" 4110 4111 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4112 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4113 4114 def opclass_sql(self, expression: exp.Opclass) -> str: 4115 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4116 4117 def predict_sql(self, expression: exp.Predict) -> str: 4118 model = self.sql(expression, "this") 4119 model = f"MODEL {model}" 4120 table = self.sql(expression, "expression") 4121 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4122 parameters = self.sql(expression, "params_struct") 4123 return self.func("PREDICT", model, table, parameters or None) 4124 4125 def forin_sql(self, expression: exp.ForIn) -> str: 4126 this = self.sql(expression, "this") 4127 expression_sql = self.sql(expression, "expression") 4128 return f"FOR {this} DO {expression_sql}" 4129 4130 def refresh_sql(self, expression: exp.Refresh) -> str: 4131 this = self.sql(expression, "this") 4132 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4133 return f"REFRESH {table}{this}" 4134 4135 def toarray_sql(self, expression: exp.ToArray) -> str: 4136 arg = expression.this 4137 if not arg.type: 4138 from sqlglot.optimizer.annotate_types import annotate_types 4139 4140 arg = annotate_types(arg, dialect=self.dialect) 4141 4142 if arg.is_type(exp.DataType.Type.ARRAY): 4143 return self.sql(arg) 4144 4145 cond_for_null = arg.is_(exp.null()) 4146 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4147 4148 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4149 this = expression.this 4150 time_format = self.format_time(expression) 4151 4152 if time_format: 4153 return self.sql( 4154 exp.cast( 4155 exp.StrToTime(this=this, format=expression.args["format"]), 4156 exp.DataType.Type.TIME, 4157 ) 4158 ) 4159 4160 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4161 return self.sql(this) 4162 4163 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4164 4165 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4166 this = expression.this 4167 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4168 return self.sql(this) 4169 4170 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4171 4172 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4173 this = expression.this 4174 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4175 return self.sql(this) 4176 4177 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4178 4179 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4180 this = expression.this 4181 time_format = self.format_time(expression) 4182 4183 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4184 return self.sql( 4185 exp.cast( 4186 exp.StrToTime(this=this, format=expression.args["format"]), 4187 exp.DataType.Type.DATE, 4188 ) 4189 ) 4190 4191 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4192 return self.sql(this) 4193 4194 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4195 4196 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4197 return self.sql( 4198 exp.func( 4199 "DATEDIFF", 4200 expression.this, 4201 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4202 "day", 4203 ) 4204 ) 4205 4206 def lastday_sql(self, expression: exp.LastDay) -> str: 4207 if self.LAST_DAY_SUPPORTS_DATE_PART: 4208 return self.function_fallback_sql(expression) 4209 4210 unit = expression.text("unit") 4211 if unit and unit != "MONTH": 4212 self.unsupported("Date parts are not supported in LAST_DAY.") 4213 4214 return self.func("LAST_DAY", expression.this) 4215 4216 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4217 from sqlglot.dialects.dialect import unit_to_str 4218 4219 return self.func( 4220 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4221 ) 4222 4223 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4224 if self.CAN_IMPLEMENT_ARRAY_ANY: 4225 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4226 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4227 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4228 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4229 4230 from sqlglot.dialects import Dialect 4231 4232 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4233 if self.dialect.__class__ != Dialect: 4234 self.unsupported("ARRAY_ANY is unsupported") 4235 4236 return self.function_fallback_sql(expression) 4237 4238 def struct_sql(self, expression: exp.Struct) -> str: 4239 expression.set( 4240 "expressions", 4241 [ 4242 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4243 if isinstance(e, exp.PropertyEQ) 4244 else e 4245 for e in expression.expressions 4246 ], 4247 ) 4248 4249 return self.function_fallback_sql(expression) 4250 4251 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4252 low = self.sql(expression, "this") 4253 high = self.sql(expression, "expression") 4254 4255 return f"{low} TO {high}" 4256 4257 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4258 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4259 tables = f" {self.expressions(expression)}" 4260 4261 exists = " IF EXISTS" if expression.args.get("exists") else "" 4262 4263 on_cluster = self.sql(expression, "cluster") 4264 on_cluster = f" {on_cluster}" if on_cluster else "" 4265 4266 identity = self.sql(expression, "identity") 4267 identity = f" {identity} IDENTITY" if identity else "" 4268 4269 option = self.sql(expression, "option") 4270 option = f" {option}" if option else "" 4271 4272 partition = self.sql(expression, "partition") 4273 partition = f" {partition}" if partition else "" 4274 4275 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4276 4277 # This transpiles T-SQL's CONVERT function 4278 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4279 def convert_sql(self, expression: exp.Convert) -> str: 4280 to = expression.this 4281 value = expression.expression 4282 style = expression.args.get("style") 4283 safe = expression.args.get("safe") 4284 strict = expression.args.get("strict") 4285 4286 if not to or not value: 4287 return "" 4288 4289 # Retrieve length of datatype and override to default if not specified 4290 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4291 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4292 4293 transformed: t.Optional[exp.Expression] = None 4294 cast = exp.Cast if strict else exp.TryCast 4295 4296 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4297 if isinstance(style, exp.Literal) and style.is_int: 4298 from sqlglot.dialects.tsql import TSQL 4299 4300 style_value = style.name 4301 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4302 if not converted_style: 4303 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4304 4305 fmt = exp.Literal.string(converted_style) 4306 4307 if to.this == exp.DataType.Type.DATE: 4308 transformed = exp.StrToDate(this=value, format=fmt) 4309 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4310 transformed = exp.StrToTime(this=value, format=fmt) 4311 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4312 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4313 elif to.this == exp.DataType.Type.TEXT: 4314 transformed = exp.TimeToStr(this=value, format=fmt) 4315 4316 if not transformed: 4317 transformed = cast(this=value, to=to, safe=safe) 4318 4319 return self.sql(transformed) 4320 4321 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4322 this = expression.this 4323 if isinstance(this, exp.JSONPathWildcard): 4324 this = self.json_path_part(this) 4325 return f".{this}" if this else "" 4326 4327 if exp.SAFE_IDENTIFIER_RE.match(this): 4328 return f".{this}" 4329 4330 this = self.json_path_part(this) 4331 return ( 4332 f"[{this}]" 4333 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4334 else f".{this}" 4335 ) 4336 4337 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4338 this = self.json_path_part(expression.this) 4339 return f"[{this}]" if this else "" 4340 4341 def _simplify_unless_literal(self, expression: E) -> E: 4342 if not isinstance(expression, exp.Literal): 4343 from sqlglot.optimizer.simplify import simplify 4344 4345 expression = simplify(expression, dialect=self.dialect) 4346 4347 return expression 4348 4349 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4350 this = expression.this 4351 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4352 self.unsupported( 4353 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4354 ) 4355 return self.sql(this) 4356 4357 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4358 # The first modifier here will be the one closest to the AggFunc's arg 4359 mods = sorted( 4360 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4361 key=lambda x: 0 4362 if isinstance(x, exp.HavingMax) 4363 else (1 if isinstance(x, exp.Order) else 2), 4364 ) 4365 4366 if mods: 4367 mod = mods[0] 4368 this = expression.__class__(this=mod.this.copy()) 4369 this.meta["inline"] = True 4370 mod.this.replace(this) 4371 return self.sql(expression.this) 4372 4373 agg_func = expression.find(exp.AggFunc) 4374 4375 if agg_func: 4376 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4377 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4378 4379 return f"{self.sql(expression, 'this')} {text}" 4380 4381 def _replace_line_breaks(self, string: str) -> str: 4382 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4383 if self.pretty: 4384 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4385 return string 4386 4387 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4388 option = self.sql(expression, "this") 4389 4390 if expression.expressions: 4391 upper = option.upper() 4392 4393 # Snowflake FILE_FORMAT options are separated by whitespace 4394 sep = " " if upper == "FILE_FORMAT" else ", " 4395 4396 # Databricks copy/format options do not set their list of values with EQ 4397 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4398 values = self.expressions(expression, flat=True, sep=sep) 4399 return f"{option}{op}({values})" 4400 4401 value = self.sql(expression, "expression") 4402 4403 if not value: 4404 return option 4405 4406 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4407 4408 return f"{option}{op}{value}" 4409 4410 def credentials_sql(self, expression: exp.Credentials) -> str: 4411 cred_expr = expression.args.get("credentials") 4412 if isinstance(cred_expr, exp.Literal): 4413 # Redshift case: CREDENTIALS <string> 4414 credentials = self.sql(expression, "credentials") 4415 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4416 else: 4417 # Snowflake case: CREDENTIALS = (...) 4418 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4419 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4420 4421 storage = self.sql(expression, "storage") 4422 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4423 4424 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4425 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4426 4427 iam_role = self.sql(expression, "iam_role") 4428 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4429 4430 region = self.sql(expression, "region") 4431 region = f" REGION {region}" if region else "" 4432 4433 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4434 4435 def copy_sql(self, expression: exp.Copy) -> str: 4436 this = self.sql(expression, "this") 4437 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4438 4439 credentials = self.sql(expression, "credentials") 4440 credentials = self.seg(credentials) if credentials else "" 4441 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4442 files = self.expressions(expression, key="files", flat=True) 4443 4444 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4445 params = self.expressions( 4446 expression, 4447 key="params", 4448 sep=sep, 4449 new_line=True, 4450 skip_last=True, 4451 skip_first=True, 4452 indent=self.COPY_PARAMS_ARE_WRAPPED, 4453 ) 4454 4455 if params: 4456 if self.COPY_PARAMS_ARE_WRAPPED: 4457 params = f" WITH ({params})" 4458 elif not self.pretty: 4459 params = f" {params}" 4460 4461 return f"COPY{this}{kind} {files}{credentials}{params}" 4462 4463 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4464 return "" 4465 4466 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4467 on_sql = "ON" if expression.args.get("on") else "OFF" 4468 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4469 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4470 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4471 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4472 4473 if filter_col or retention_period: 4474 on_sql = self.func("ON", filter_col, retention_period) 4475 4476 return f"DATA_DELETION={on_sql}" 4477 4478 def maskingpolicycolumnconstraint_sql( 4479 self, expression: exp.MaskingPolicyColumnConstraint 4480 ) -> str: 4481 this = self.sql(expression, "this") 4482 expressions = self.expressions(expression, flat=True) 4483 expressions = f" USING ({expressions})" if expressions else "" 4484 return f"MASKING POLICY {this}{expressions}" 4485 4486 def gapfill_sql(self, expression: exp.GapFill) -> str: 4487 this = self.sql(expression, "this") 4488 this = f"TABLE {this}" 4489 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4490 4491 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4492 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4493 4494 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4495 this = self.sql(expression, "this") 4496 expr = expression.expression 4497 4498 if isinstance(expr, exp.Func): 4499 # T-SQL's CLR functions are case sensitive 4500 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4501 else: 4502 expr = self.sql(expression, "expression") 4503 4504 return self.scope_resolution(expr, this) 4505 4506 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4507 if self.PARSE_JSON_NAME is None: 4508 return self.sql(expression.this) 4509 4510 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4511 4512 def rand_sql(self, expression: exp.Rand) -> str: 4513 lower = self.sql(expression, "lower") 4514 upper = self.sql(expression, "upper") 4515 4516 if lower and upper: 4517 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4518 return self.func("RAND", expression.this) 4519 4520 def changes_sql(self, expression: exp.Changes) -> str: 4521 information = self.sql(expression, "information") 4522 information = f"INFORMATION => {information}" 4523 at_before = self.sql(expression, "at_before") 4524 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4525 end = self.sql(expression, "end") 4526 end = f"{self.seg('')}{end}" if end else "" 4527 4528 return f"CHANGES ({information}){at_before}{end}" 4529 4530 def pad_sql(self, expression: exp.Pad) -> str: 4531 prefix = "L" if expression.args.get("is_left") else "R" 4532 4533 fill_pattern = self.sql(expression, "fill_pattern") or None 4534 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4535 fill_pattern = "' '" 4536 4537 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4538 4539 def summarize_sql(self, expression: exp.Summarize) -> str: 4540 table = " TABLE" if expression.args.get("table") else "" 4541 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4542 4543 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4544 generate_series = exp.GenerateSeries(**expression.args) 4545 4546 parent = expression.parent 4547 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4548 parent = parent.parent 4549 4550 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4551 return self.sql(exp.Unnest(expressions=[generate_series])) 4552 4553 if isinstance(parent, exp.Select): 4554 self.unsupported("GenerateSeries projection unnesting is not supported.") 4555 4556 return self.sql(generate_series) 4557 4558 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4559 exprs = expression.expressions 4560 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4561 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4562 else: 4563 rhs = self.expressions(expression) 4564 4565 return self.func(name, expression.this, rhs or None) 4566 4567 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4568 if self.SUPPORTS_CONVERT_TIMEZONE: 4569 return self.function_fallback_sql(expression) 4570 4571 source_tz = expression.args.get("source_tz") 4572 target_tz = expression.args.get("target_tz") 4573 timestamp = expression.args.get("timestamp") 4574 4575 if source_tz and timestamp: 4576 timestamp = exp.AtTimeZone( 4577 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4578 ) 4579 4580 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4581 4582 return self.sql(expr) 4583 4584 def json_sql(self, expression: exp.JSON) -> str: 4585 this = self.sql(expression, "this") 4586 this = f" {this}" if this else "" 4587 4588 _with = expression.args.get("with") 4589 4590 if _with is None: 4591 with_sql = "" 4592 elif not _with: 4593 with_sql = " WITHOUT" 4594 else: 4595 with_sql = " WITH" 4596 4597 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4598 4599 return f"JSON{this}{with_sql}{unique_sql}" 4600 4601 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4602 def _generate_on_options(arg: t.Any) -> str: 4603 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4604 4605 path = self.sql(expression, "path") 4606 returning = self.sql(expression, "returning") 4607 returning = f" RETURNING {returning}" if returning else "" 4608 4609 on_condition = self.sql(expression, "on_condition") 4610 on_condition = f" {on_condition}" if on_condition else "" 4611 4612 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4613 4614 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4615 else_ = "ELSE " if expression.args.get("else_") else "" 4616 condition = self.sql(expression, "expression") 4617 condition = f"WHEN {condition} THEN " if condition else else_ 4618 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4619 return f"{condition}{insert}" 4620 4621 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4622 kind = self.sql(expression, "kind") 4623 expressions = self.seg(self.expressions(expression, sep=" ")) 4624 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4625 return res 4626 4627 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4628 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4629 empty = expression.args.get("empty") 4630 empty = ( 4631 f"DEFAULT {empty} ON EMPTY" 4632 if isinstance(empty, exp.Expression) 4633 else self.sql(expression, "empty") 4634 ) 4635 4636 error = expression.args.get("error") 4637 error = ( 4638 f"DEFAULT {error} ON ERROR" 4639 if isinstance(error, exp.Expression) 4640 else self.sql(expression, "error") 4641 ) 4642 4643 if error and empty: 4644 error = ( 4645 f"{empty} {error}" 4646 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4647 else f"{error} {empty}" 4648 ) 4649 empty = "" 4650 4651 null = self.sql(expression, "null") 4652 4653 return f"{empty}{error}{null}" 4654 4655 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4656 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4657 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4658 4659 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4660 this = self.sql(expression, "this") 4661 path = self.sql(expression, "path") 4662 4663 passing = self.expressions(expression, "passing") 4664 passing = f" PASSING {passing}" if passing else "" 4665 4666 on_condition = self.sql(expression, "on_condition") 4667 on_condition = f" {on_condition}" if on_condition else "" 4668 4669 path = f"{path}{passing}{on_condition}" 4670 4671 return self.func("JSON_EXISTS", this, path) 4672 4673 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4674 array_agg = self.function_fallback_sql(expression) 4675 4676 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4677 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4678 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4679 parent = expression.parent 4680 if isinstance(parent, exp.Filter): 4681 parent_cond = parent.expression.this 4682 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4683 else: 4684 this = expression.this 4685 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4686 if this.find(exp.Column): 4687 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4688 this_sql = ( 4689 self.expressions(this) 4690 if isinstance(this, exp.Distinct) 4691 else self.sql(expression, "this") 4692 ) 4693 4694 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4695 4696 return array_agg 4697 4698 def apply_sql(self, expression: exp.Apply) -> str: 4699 this = self.sql(expression, "this") 4700 expr = self.sql(expression, "expression") 4701 4702 return f"{this} APPLY({expr})" 4703 4704 def grant_sql(self, expression: exp.Grant) -> str: 4705 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4706 4707 kind = self.sql(expression, "kind") 4708 kind = f" {kind}" if kind else "" 4709 4710 securable = self.sql(expression, "securable") 4711 securable = f" {securable}" if securable else "" 4712 4713 principals = self.expressions(expression, key="principals", flat=True) 4714 4715 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4716 4717 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4718 4719 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4720 this = self.sql(expression, "this") 4721 columns = self.expressions(expression, flat=True) 4722 columns = f"({columns})" if columns else "" 4723 4724 return f"{this}{columns}" 4725 4726 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4727 this = self.sql(expression, "this") 4728 4729 kind = self.sql(expression, "kind") 4730 kind = f"{kind} " if kind else "" 4731 4732 return f"{kind}{this}" 4733 4734 def columns_sql(self, expression: exp.Columns): 4735 func = self.function_fallback_sql(expression) 4736 if expression.args.get("unpack"): 4737 func = f"*{func}" 4738 4739 return func 4740 4741 def overlay_sql(self, expression: exp.Overlay): 4742 this = self.sql(expression, "this") 4743 expr = self.sql(expression, "expression") 4744 from_sql = self.sql(expression, "from") 4745 for_sql = self.sql(expression, "for") 4746 for_sql = f" FOR {for_sql}" if for_sql else "" 4747 4748 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4749 4750 @unsupported_args("format") 4751 def todouble_sql(self, expression: exp.ToDouble) -> str: 4752 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4753 4754 def string_sql(self, expression: exp.String) -> str: 4755 this = expression.this 4756 zone = expression.args.get("zone") 4757 4758 if zone: 4759 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4760 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4761 # set for source_tz to transpile the time conversion before the STRING cast 4762 this = exp.ConvertTimezone( 4763 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4764 ) 4765 4766 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4767 4768 def median_sql(self, expression: exp.Median): 4769 if not self.SUPPORTS_MEDIAN: 4770 return self.sql( 4771 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4772 ) 4773 4774 return self.function_fallback_sql(expression) 4775 4776 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4777 filler = self.sql(expression, "this") 4778 filler = f" {filler}" if filler else "" 4779 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4780 return f"TRUNCATE{filler} {with_count}" 4781 4782 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4783 if self.SUPPORTS_UNIX_SECONDS: 4784 return self.function_fallback_sql(expression) 4785 4786 start_ts = exp.cast( 4787 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4788 ) 4789 4790 return self.sql( 4791 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4792 ) 4793 4794 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4795 dim = expression.expression 4796 4797 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4798 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4799 if not (dim.is_int and dim.name == "1"): 4800 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4801 dim = None 4802 4803 # If dimension is required but not specified, default initialize it 4804 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4805 dim = exp.Literal.number(1) 4806 4807 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4808 4809 def attach_sql(self, expression: exp.Attach) -> str: 4810 this = self.sql(expression, "this") 4811 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4812 expressions = self.expressions(expression) 4813 expressions = f" ({expressions})" if expressions else "" 4814 4815 return f"ATTACH{exists_sql} {this}{expressions}" 4816 4817 def detach_sql(self, expression: exp.Detach) -> str: 4818 this = self.sql(expression, "this") 4819 # the DATABASE keyword is required if IF EXISTS is set 4820 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4821 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4822 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4823 4824 return f"DETACH{exists_sql} {this}" 4825 4826 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4827 this = self.sql(expression, "this") 4828 value = self.sql(expression, "expression") 4829 value = f" {value}" if value else "" 4830 return f"{this}{value}" 4831 4832 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4833 this_sql = self.sql(expression, "this") 4834 if isinstance(expression.this, exp.Table): 4835 this_sql = f"TABLE {this_sql}" 4836 4837 return self.func( 4838 "FEATURES_AT_TIME", 4839 this_sql, 4840 expression.args.get("time"), 4841 expression.args.get("num_rows"), 4842 expression.args.get("ignore_feature_nulls"), 4843 ) 4844 4845 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4846 return ( 4847 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4848 ) 4849 4850 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4851 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4852 encode = f"{encode} {self.sql(expression, 'this')}" 4853 4854 properties = expression.args.get("properties") 4855 if properties: 4856 encode = f"{encode} {self.properties(properties)}" 4857 4858 return encode 4859 4860 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4861 this = self.sql(expression, "this") 4862 include = f"INCLUDE {this}" 4863 4864 column_def = self.sql(expression, "column_def") 4865 if column_def: 4866 include = f"{include} {column_def}" 4867 4868 alias = self.sql(expression, "alias") 4869 if alias: 4870 include = f"{include} AS {alias}" 4871 4872 return include 4873 4874 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4875 name = f"NAME {self.sql(expression, 'this')}" 4876 return self.func("XMLELEMENT", name, *expression.expressions) 4877 4878 def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str: 4879 this = self.sql(expression, "this") 4880 expr = self.sql(expression, "expression") 4881 expr = f"({expr})" if expr else "" 4882 return f"{this}{expr}" 4883 4884 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4885 partitions = self.expressions(expression, "partition_expressions") 4886 create = self.expressions(expression, "create_expressions") 4887 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4888 4889 def partitionbyrangepropertydynamic_sql( 4890 self, expression: exp.PartitionByRangePropertyDynamic 4891 ) -> str: 4892 start = self.sql(expression, "start") 4893 end = self.sql(expression, "end") 4894 4895 every = expression.args["every"] 4896 if isinstance(every, exp.Interval) and every.this.is_string: 4897 every.this.replace(exp.Literal.number(every.name)) 4898 4899 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4900 4901 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4902 name = self.sql(expression, "this") 4903 values = self.expressions(expression, flat=True) 4904 4905 return f"NAME {name} VALUE {values}" 4906 4907 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4908 kind = self.sql(expression, "kind") 4909 sample = self.sql(expression, "sample") 4910 return f"SAMPLE {sample} {kind}" 4911 4912 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4913 kind = self.sql(expression, "kind") 4914 option = self.sql(expression, "option") 4915 option = f" {option}" if option else "" 4916 this = self.sql(expression, "this") 4917 this = f" {this}" if this else "" 4918 columns = self.expressions(expression) 4919 columns = f" {columns}" if columns else "" 4920 return f"{kind}{option} STATISTICS{this}{columns}" 4921 4922 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4923 this = self.sql(expression, "this") 4924 columns = self.expressions(expression) 4925 inner_expression = self.sql(expression, "expression") 4926 inner_expression = f" {inner_expression}" if inner_expression else "" 4927 update_options = self.sql(expression, "update_options") 4928 update_options = f" {update_options} UPDATE" if update_options else "" 4929 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4930 4931 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4932 kind = self.sql(expression, "kind") 4933 kind = f" {kind}" if kind else "" 4934 return f"DELETE{kind} STATISTICS" 4935 4936 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4937 inner_expression = self.sql(expression, "expression") 4938 return f"LIST CHAINED ROWS{inner_expression}" 4939 4940 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4941 kind = self.sql(expression, "kind") 4942 this = self.sql(expression, "this") 4943 this = f" {this}" if this else "" 4944 inner_expression = self.sql(expression, "expression") 4945 return f"VALIDATE {kind}{this}{inner_expression}" 4946 4947 def analyze_sql(self, expression: exp.Analyze) -> str: 4948 options = self.expressions(expression, key="options", sep=" ") 4949 options = f" {options}" if options else "" 4950 kind = self.sql(expression, "kind") 4951 kind = f" {kind}" if kind else "" 4952 this = self.sql(expression, "this") 4953 this = f" {this}" if this else "" 4954 mode = self.sql(expression, "mode") 4955 mode = f" {mode}" if mode else "" 4956 properties = self.sql(expression, "properties") 4957 properties = f" {properties}" if properties else "" 4958 partition = self.sql(expression, "partition") 4959 partition = f" {partition}" if partition else "" 4960 inner_expression = self.sql(expression, "expression") 4961 inner_expression = f" {inner_expression}" if inner_expression else "" 4962 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4963 4964 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4965 this = self.sql(expression, "this") 4966 namespaces = self.expressions(expression, key="namespaces") 4967 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4968 passing = self.expressions(expression, key="passing") 4969 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4970 columns = self.expressions(expression, key="columns") 4971 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4972 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4973 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4974 4975 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4976 this = self.sql(expression, "this") 4977 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4978 4979 def export_sql(self, expression: exp.Export) -> str: 4980 this = self.sql(expression, "this") 4981 connection = self.sql(expression, "connection") 4982 connection = f"WITH CONNECTION {connection} " if connection else "" 4983 options = self.sql(expression, "options") 4984 return f"EXPORT DATA {connection}{options} AS {this}" 4985 4986 def declare_sql(self, expression: exp.Declare) -> str: 4987 return f"DECLARE {self.expressions(expression, flat=True)}" 4988 4989 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4990 variable = self.sql(expression, "this") 4991 default = self.sql(expression, "default") 4992 default = f" = {default}" if default else "" 4993 4994 kind = self.sql(expression, "kind") 4995 if isinstance(expression.args.get("kind"), exp.Schema): 4996 kind = f"TABLE {kind}" 4997 4998 return f"{variable} AS {kind}{default}" 4999 5000 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5001 kind = self.sql(expression, "kind") 5002 this = self.sql(expression, "this") 5003 set = self.sql(expression, "expression") 5004 using = self.sql(expression, "using") 5005 using = f" USING {using}" if using else "" 5006 5007 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5008 5009 return f"{kind_sql} {this} SET {set}{using}" 5010 5011 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 5012 params = self.expressions(expression, key="params", flat=True) 5013 return self.func(expression.name, *expression.expressions) + f"({params})" 5014 5015 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 5016 return self.func(expression.name, *expression.expressions) 5017 5018 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 5019 return self.anonymousaggfunc_sql(expression) 5020 5021 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 5022 return self.parameterizedagg_sql(expression) 5023 5024 def show_sql(self, expression: exp.Show) -> str: 5025 self.unsupported("Unsupported SHOW statement") 5026 return "" 5027 5028 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5029 # Snowflake GET/PUT statements: 5030 # PUT <file> <internalStage> <properties> 5031 # GET <internalStage> <file> <properties> 5032 props = expression.args.get("properties") 5033 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5034 this = self.sql(expression, "this") 5035 target = self.sql(expression, "target") 5036 5037 if isinstance(expression, exp.Put): 5038 return f"PUT {this} {target}{props_sql}" 5039 else: 5040 return f"GET {target} {this}{props_sql}" 5041 5042 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 5043 this = self.sql(expression, "this") 5044 expr = self.sql(expression, "expression") 5045 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 5046 return f"TRANSLATE({this} USING {expr}{with_error})" 5047 5048 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5049 if self.SUPPORTS_DECODE_CASE: 5050 return self.func("DECODE", *expression.expressions) 5051 5052 expression, *expressions = expression.expressions 5053 5054 ifs = [] 5055 for search, result in zip(expressions[::2], expressions[1::2]): 5056 if isinstance(search, exp.Literal): 5057 ifs.append(exp.If(this=expression.eq(search), true=result)) 5058 elif isinstance(search, exp.Null): 5059 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5060 else: 5061 if isinstance(search, exp.Binary): 5062 search = exp.paren(search) 5063 5064 cond = exp.or_( 5065 expression.eq(search), 5066 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5067 copy=False, 5068 ) 5069 ifs.append(exp.If(this=cond, true=result)) 5070 5071 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5072 return self.sql(case) 5073 5074 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5075 this = self.sql(expression, "this") 5076 this = self.seg(this, sep="") 5077 dimensions = self.expressions( 5078 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5079 ) 5080 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5081 metrics = self.expressions( 5082 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5083 ) 5084 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5085 where = self.sql(expression, "where") 5086 where = self.seg(f"WHERE {where}") if where else "" 5087 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
716 def __init__( 717 self, 718 pretty: t.Optional[bool] = None, 719 identify: str | bool = False, 720 normalize: bool = False, 721 pad: int = 2, 722 indent: int = 2, 723 normalize_functions: t.Optional[str | bool] = None, 724 unsupported_level: ErrorLevel = ErrorLevel.WARN, 725 max_unsupported: int = 3, 726 leading_comma: bool = False, 727 max_text_width: int = 80, 728 comments: bool = True, 729 dialect: DialectType = None, 730 ): 731 import sqlglot 732 from sqlglot.dialects import Dialect 733 734 self.pretty = pretty if pretty is not None else sqlglot.pretty 735 self.identify = identify 736 self.normalize = normalize 737 self.pad = pad 738 self._indent = indent 739 self.unsupported_level = unsupported_level 740 self.max_unsupported = max_unsupported 741 self.leading_comma = leading_comma 742 self.max_text_width = max_text_width 743 self.comments = comments 744 self.dialect = Dialect.get_or_raise(dialect) 745 746 # This is both a Dialect property and a Generator argument, so we prioritize the latter 747 self.normalize_functions = ( 748 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 749 ) 750 751 self.unsupported_messages: t.List[str] = [] 752 self._escaped_quote_end: str = ( 753 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 754 ) 755 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 756 757 self._next_name = name_sequence("_t") 758 759 self._identifier_start = self.dialect.IDENTIFIER_START 760 self._identifier_end = self.dialect.IDENTIFIER_END 761 762 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PositionalColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TableColumn'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathSubscript'>, <class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Order'>, <class 'sqlglot.expressions.Group'>, <class 'sqlglot.expressions.Having'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>, <Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>}
764 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 765 """ 766 Generates the SQL string corresponding to the given syntax tree. 767 768 Args: 769 expression: The syntax tree. 770 copy: Whether to copy the expression. The generator performs mutations so 771 it is safer to copy. 772 773 Returns: 774 The SQL string corresponding to `expression`. 775 """ 776 if copy: 777 expression = expression.copy() 778 779 expression = self.preprocess(expression) 780 781 self.unsupported_messages = [] 782 sql = self.sql(expression).strip() 783 784 if self.pretty: 785 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 786 787 if self.unsupported_level == ErrorLevel.IGNORE: 788 return sql 789 790 if self.unsupported_level == ErrorLevel.WARN: 791 for msg in self.unsupported_messages: 792 logger.warning(msg) 793 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 794 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 795 796 return sql
Generates the SQL string corresponding to the given syntax tree.
Arguments:
- expression: The syntax tree.
- copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:
The SQL string corresponding to
expression.
def
preprocess( self, expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression:
798 def preprocess(self, expression: exp.Expression) -> exp.Expression: 799 """Apply generic preprocessing transformations to a given expression.""" 800 expression = self._move_ctes_to_top_level(expression) 801 802 if self.ENSURE_BOOLS: 803 from sqlglot.transforms import ensure_bools 804 805 expression = ensure_bools(expression) 806 807 return expression
Apply generic preprocessing transformations to a given expression.
def
sanitize_comment(self, comment: str) -> str:
831 def sanitize_comment(self, comment: str) -> str: 832 comment = " " + comment if comment[0].strip() else comment 833 comment = comment + " " if comment[-1].strip() else comment 834 835 if not self.dialect.tokenizer_class.NESTED_COMMENTS: 836 # Necessary workaround to avoid syntax errors due to nesting: /* ... */ ... */ 837 comment = comment.replace("*/", "* /") 838 839 return comment
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
841 def maybe_comment( 842 self, 843 sql: str, 844 expression: t.Optional[exp.Expression] = None, 845 comments: t.Optional[t.List[str]] = None, 846 separated: bool = False, 847 ) -> str: 848 comments = ( 849 ((expression and expression.comments) if comments is None else comments) # type: ignore 850 if self.comments 851 else None 852 ) 853 854 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 855 return sql 856 857 comments_sql = " ".join( 858 f"/*{self.sanitize_comment(comment)}*/" for comment in comments if comment 859 ) 860 861 if not comments_sql: 862 return sql 863 864 comments_sql = self._replace_line_breaks(comments_sql) 865 866 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 867 return ( 868 f"{self.sep()}{comments_sql}{sql}" 869 if not sql or sql[0].isspace() 870 else f"{comments_sql}{self.sep()}{sql}" 871 ) 872 873 return f"{sql} {comments_sql}"
875 def wrap(self, expression: exp.Expression | str) -> str: 876 this_sql = ( 877 self.sql(expression) 878 if isinstance(expression, exp.UNWRAPPED_QUERIES) 879 else self.sql(expression, "this") 880 ) 881 if not this_sql: 882 return "()" 883 884 this_sql = self.indent(this_sql, level=1, pad=0) 885 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
901 def indent( 902 self, 903 sql: str, 904 level: int = 0, 905 pad: t.Optional[int] = None, 906 skip_first: bool = False, 907 skip_last: bool = False, 908 ) -> str: 909 if not self.pretty or not sql: 910 return sql 911 912 pad = self.pad if pad is None else pad 913 lines = sql.split("\n") 914 915 return "\n".join( 916 ( 917 line 918 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 919 else f"{' ' * (level * self._indent + pad)}{line}" 920 ) 921 for i, line in enumerate(lines) 922 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
924 def sql( 925 self, 926 expression: t.Optional[str | exp.Expression], 927 key: t.Optional[str] = None, 928 comment: bool = True, 929 ) -> str: 930 if not expression: 931 return "" 932 933 if isinstance(expression, str): 934 return expression 935 936 if key: 937 value = expression.args.get(key) 938 if value: 939 return self.sql(value) 940 return "" 941 942 transform = self.TRANSFORMS.get(expression.__class__) 943 944 if callable(transform): 945 sql = transform(self, expression) 946 elif isinstance(expression, exp.Expression): 947 exp_handler_name = f"{expression.key}_sql" 948 949 if hasattr(self, exp_handler_name): 950 sql = getattr(self, exp_handler_name)(expression) 951 elif isinstance(expression, exp.Func): 952 sql = self.function_fallback_sql(expression) 953 elif isinstance(expression, exp.Property): 954 sql = self.property_sql(expression) 955 else: 956 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 957 else: 958 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 959 960 return self.maybe_comment(sql, expression) if self.comments and comment else sql
967 def cache_sql(self, expression: exp.Cache) -> str: 968 lazy = " LAZY" if expression.args.get("lazy") else "" 969 table = self.sql(expression, "this") 970 options = expression.args.get("options") 971 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 972 sql = self.sql(expression, "expression") 973 sql = f" AS{self.sep()}{sql}" if sql else "" 974 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 975 return self.prepend_ctes(expression, sql)
977 def characterset_sql(self, expression: exp.CharacterSet) -> str: 978 if isinstance(expression.parent, exp.Cast): 979 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 980 default = "DEFAULT " if expression.args.get("default") else "" 981 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
995 def column_sql(self, expression: exp.Column) -> str: 996 join_mark = " (+)" if expression.args.get("join_mark") else "" 997 998 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 999 join_mark = "" 1000 self.unsupported("Outer join syntax using the (+) operator is not supported.") 1001 1002 return f"{self.column_parts(expression)}{join_mark}"
1010 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 1011 column = self.sql(expression, "this") 1012 kind = self.sql(expression, "kind") 1013 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 1014 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 1015 kind = f"{sep}{kind}" if kind else "" 1016 constraints = f" {constraints}" if constraints else "" 1017 position = self.sql(expression, "position") 1018 position = f" {position}" if position else "" 1019 1020 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 1021 kind = "" 1022 1023 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1030 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1031 this = self.sql(expression, "this") 1032 if expression.args.get("not_null"): 1033 persisted = " PERSISTED NOT NULL" 1034 elif expression.args.get("persisted"): 1035 persisted = " PERSISTED" 1036 else: 1037 persisted = "" 1038 1039 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1052 def generatedasidentitycolumnconstraint_sql( 1053 self, expression: exp.GeneratedAsIdentityColumnConstraint 1054 ) -> str: 1055 this = "" 1056 if expression.this is not None: 1057 on_null = " ON NULL" if expression.args.get("on_null") else "" 1058 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1059 1060 start = expression.args.get("start") 1061 start = f"START WITH {start}" if start else "" 1062 increment = expression.args.get("increment") 1063 increment = f" INCREMENT BY {increment}" if increment else "" 1064 minvalue = expression.args.get("minvalue") 1065 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1066 maxvalue = expression.args.get("maxvalue") 1067 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1068 cycle = expression.args.get("cycle") 1069 cycle_sql = "" 1070 1071 if cycle is not None: 1072 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1073 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1074 1075 sequence_opts = "" 1076 if start or increment or cycle_sql: 1077 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1078 sequence_opts = f" ({sequence_opts.strip()})" 1079 1080 expr = self.sql(expression, "expression") 1081 expr = f"({expr})" if expr else "IDENTITY" 1082 1083 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1085 def generatedasrowcolumnconstraint_sql( 1086 self, expression: exp.GeneratedAsRowColumnConstraint 1087 ) -> str: 1088 start = "START" if expression.args.get("start") else "END" 1089 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1090 return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def
periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
def
notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
1100 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1101 desc = expression.args.get("desc") 1102 if desc is not None: 1103 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1104 options = self.expressions(expression, key="options", flat=True, sep=" ") 1105 options = f" {options}" if options else "" 1106 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1108 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1109 this = self.sql(expression, "this") 1110 this = f" {this}" if this else "" 1111 index_type = expression.args.get("index_type") 1112 index_type = f" USING {index_type}" if index_type else "" 1113 on_conflict = self.sql(expression, "on_conflict") 1114 on_conflict = f" {on_conflict}" if on_conflict else "" 1115 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1116 options = self.expressions(expression, key="options", flat=True, sep=" ") 1117 options = f" {options}" if options else "" 1118 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1123 def create_sql(self, expression: exp.Create) -> str: 1124 kind = self.sql(expression, "kind") 1125 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1126 properties = expression.args.get("properties") 1127 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1128 1129 this = self.createable_sql(expression, properties_locs) 1130 1131 properties_sql = "" 1132 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1133 exp.Properties.Location.POST_WITH 1134 ): 1135 properties_sql = self.sql( 1136 exp.Properties( 1137 expressions=[ 1138 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1139 *properties_locs[exp.Properties.Location.POST_WITH], 1140 ] 1141 ) 1142 ) 1143 1144 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1145 properties_sql = self.sep() + properties_sql 1146 elif not self.pretty: 1147 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1148 properties_sql = f" {properties_sql}" 1149 1150 begin = " BEGIN" if expression.args.get("begin") else "" 1151 end = " END" if expression.args.get("end") else "" 1152 1153 expression_sql = self.sql(expression, "expression") 1154 if expression_sql: 1155 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1156 1157 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1158 postalias_props_sql = "" 1159 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1160 postalias_props_sql = self.properties( 1161 exp.Properties( 1162 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1163 ), 1164 wrapped=False, 1165 ) 1166 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1167 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1168 1169 postindex_props_sql = "" 1170 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1171 postindex_props_sql = self.properties( 1172 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1173 wrapped=False, 1174 prefix=" ", 1175 ) 1176 1177 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1178 indexes = f" {indexes}" if indexes else "" 1179 index_sql = indexes + postindex_props_sql 1180 1181 replace = " OR REPLACE" if expression.args.get("replace") else "" 1182 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1183 unique = " UNIQUE" if expression.args.get("unique") else "" 1184 1185 clustered = expression.args.get("clustered") 1186 if clustered is None: 1187 clustered_sql = "" 1188 elif clustered: 1189 clustered_sql = " CLUSTERED COLUMNSTORE" 1190 else: 1191 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1192 1193 postcreate_props_sql = "" 1194 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1195 postcreate_props_sql = self.properties( 1196 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1197 sep=" ", 1198 prefix=" ", 1199 wrapped=False, 1200 ) 1201 1202 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1203 1204 postexpression_props_sql = "" 1205 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1206 postexpression_props_sql = self.properties( 1207 exp.Properties( 1208 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1209 ), 1210 sep=" ", 1211 prefix=" ", 1212 wrapped=False, 1213 ) 1214 1215 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1216 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1217 no_schema_binding = ( 1218 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1219 ) 1220 1221 clone = self.sql(expression, "clone") 1222 clone = f" {clone}" if clone else "" 1223 1224 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1225 properties_expression = f"{expression_sql}{properties_sql}" 1226 else: 1227 properties_expression = f"{properties_sql}{expression_sql}" 1228 1229 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1230 return self.prepend_ctes(expression, expression_sql)
1232 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1233 start = self.sql(expression, "start") 1234 start = f"START WITH {start}" if start else "" 1235 increment = self.sql(expression, "increment") 1236 increment = f" INCREMENT BY {increment}" if increment else "" 1237 minvalue = self.sql(expression, "minvalue") 1238 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1239 maxvalue = self.sql(expression, "maxvalue") 1240 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1241 owned = self.sql(expression, "owned") 1242 owned = f" OWNED BY {owned}" if owned else "" 1243 1244 cache = expression.args.get("cache") 1245 if cache is None: 1246 cache_str = "" 1247 elif cache is True: 1248 cache_str = " CACHE" 1249 else: 1250 cache_str = f" CACHE {cache}" 1251 1252 options = self.expressions(expression, key="options", flat=True, sep=" ") 1253 options = f" {options}" if options else "" 1254 1255 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1257 def clone_sql(self, expression: exp.Clone) -> str: 1258 this = self.sql(expression, "this") 1259 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1260 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1261 return f"{shallow}{keyword} {this}"
1263 def describe_sql(self, expression: exp.Describe) -> str: 1264 style = expression.args.get("style") 1265 style = f" {style}" if style else "" 1266 partition = self.sql(expression, "partition") 1267 partition = f" {partition}" if partition else "" 1268 format = self.sql(expression, "format") 1269 format = f" {format}" if format else "" 1270 1271 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1283 def with_sql(self, expression: exp.With) -> str: 1284 sql = self.expressions(expression, flat=True) 1285 recursive = ( 1286 "RECURSIVE " 1287 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1288 else "" 1289 ) 1290 search = self.sql(expression, "search") 1291 search = f" {search}" if search else "" 1292 1293 return f"WITH {recursive}{sql}{search}"
1295 def cte_sql(self, expression: exp.CTE) -> str: 1296 alias = expression.args.get("alias") 1297 if alias: 1298 alias.add_comments(expression.pop_comments()) 1299 1300 alias_sql = self.sql(expression, "alias") 1301 1302 materialized = expression.args.get("materialized") 1303 if materialized is False: 1304 materialized = "NOT MATERIALIZED " 1305 elif materialized: 1306 materialized = "MATERIALIZED " 1307 1308 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1310 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1311 alias = self.sql(expression, "this") 1312 columns = self.expressions(expression, key="columns", flat=True) 1313 columns = f"({columns})" if columns else "" 1314 1315 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1316 columns = "" 1317 self.unsupported("Named columns are not supported in table alias.") 1318 1319 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1320 alias = self._next_name() 1321 1322 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1330 def hexstring_sql( 1331 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1332 ) -> str: 1333 this = self.sql(expression, "this") 1334 is_integer_type = expression.args.get("is_integer") 1335 1336 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1337 not self.dialect.HEX_START and not binary_function_repr 1338 ): 1339 # Integer representation will be returned if: 1340 # - The read dialect treats the hex value as integer literal but not the write 1341 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1342 return f"{int(this, 16)}" 1343 1344 if not is_integer_type: 1345 # Read dialect treats the hex value as BINARY/BLOB 1346 if binary_function_repr: 1347 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1348 return self.func(binary_function_repr, exp.Literal.string(this)) 1349 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1350 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1351 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1352 1353 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1361 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1362 this = self.sql(expression, "this") 1363 escape = expression.args.get("escape") 1364 1365 if self.dialect.UNICODE_START: 1366 escape_substitute = r"\\\1" 1367 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1368 else: 1369 escape_substitute = r"\\u\1" 1370 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1371 1372 if escape: 1373 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1374 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1375 else: 1376 escape_pattern = ESCAPED_UNICODE_RE 1377 escape_sql = "" 1378 1379 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1380 this = escape_pattern.sub(escape_substitute, this) 1381 1382 return f"{left_quote}{this}{right_quote}{escape_sql}"
1384 def rawstring_sql(self, expression: exp.RawString) -> str: 1385 string = expression.this 1386 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1387 string = string.replace("\\", "\\\\") 1388 1389 string = self.escape_str(string, escape_backslash=False) 1390 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1398 def datatype_sql(self, expression: exp.DataType) -> str: 1399 nested = "" 1400 values = "" 1401 interior = self.expressions(expression, flat=True) 1402 1403 type_value = expression.this 1404 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1405 type_sql = self.sql(expression, "kind") 1406 else: 1407 type_sql = ( 1408 self.TYPE_MAPPING.get(type_value, type_value.value) 1409 if isinstance(type_value, exp.DataType.Type) 1410 else type_value 1411 ) 1412 1413 if interior: 1414 if expression.args.get("nested"): 1415 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1416 if expression.args.get("values") is not None: 1417 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1418 values = self.expressions(expression, key="values", flat=True) 1419 values = f"{delimiters[0]}{values}{delimiters[1]}" 1420 elif type_value == exp.DataType.Type.INTERVAL: 1421 nested = f" {interior}" 1422 else: 1423 nested = f"({interior})" 1424 1425 type_sql = f"{type_sql}{nested}{values}" 1426 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1427 exp.DataType.Type.TIMETZ, 1428 exp.DataType.Type.TIMESTAMPTZ, 1429 ): 1430 type_sql = f"{type_sql} WITH TIME ZONE" 1431 1432 return type_sql
1434 def directory_sql(self, expression: exp.Directory) -> str: 1435 local = "LOCAL " if expression.args.get("local") else "" 1436 row_format = self.sql(expression, "row_format") 1437 row_format = f" {row_format}" if row_format else "" 1438 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1440 def delete_sql(self, expression: exp.Delete) -> str: 1441 this = self.sql(expression, "this") 1442 this = f" FROM {this}" if this else "" 1443 using = self.sql(expression, "using") 1444 using = f" USING {using}" if using else "" 1445 cluster = self.sql(expression, "cluster") 1446 cluster = f" {cluster}" if cluster else "" 1447 where = self.sql(expression, "where") 1448 returning = self.sql(expression, "returning") 1449 limit = self.sql(expression, "limit") 1450 tables = self.expressions(expression, key="tables") 1451 tables = f" {tables}" if tables else "" 1452 if self.RETURNING_END: 1453 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1454 else: 1455 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1456 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1458 def drop_sql(self, expression: exp.Drop) -> str: 1459 this = self.sql(expression, "this") 1460 expressions = self.expressions(expression, flat=True) 1461 expressions = f" ({expressions})" if expressions else "" 1462 kind = expression.args["kind"] 1463 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1464 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1465 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1466 on_cluster = self.sql(expression, "cluster") 1467 on_cluster = f" {on_cluster}" if on_cluster else "" 1468 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1469 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1470 cascade = " CASCADE" if expression.args.get("cascade") else "" 1471 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1472 purge = " PURGE" if expression.args.get("purge") else "" 1473 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1475 def set_operation(self, expression: exp.SetOperation) -> str: 1476 op_type = type(expression) 1477 op_name = op_type.key.upper() 1478 1479 distinct = expression.args.get("distinct") 1480 if ( 1481 distinct is False 1482 and op_type in (exp.Except, exp.Intersect) 1483 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1484 ): 1485 self.unsupported(f"{op_name} ALL is not supported") 1486 1487 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1488 1489 if distinct is None: 1490 distinct = default_distinct 1491 if distinct is None: 1492 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1493 1494 if distinct is default_distinct: 1495 distinct_or_all = "" 1496 else: 1497 distinct_or_all = " DISTINCT" if distinct else " ALL" 1498 1499 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1500 side_kind = f"{side_kind} " if side_kind else "" 1501 1502 by_name = " BY NAME" if expression.args.get("by_name") else "" 1503 on = self.expressions(expression, key="on", flat=True) 1504 on = f" ON ({on})" if on else "" 1505 1506 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1508 def set_operations(self, expression: exp.SetOperation) -> str: 1509 if not self.SET_OP_MODIFIERS: 1510 limit = expression.args.get("limit") 1511 order = expression.args.get("order") 1512 1513 if limit or order: 1514 select = self._move_ctes_to_top_level( 1515 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1516 ) 1517 1518 if limit: 1519 select = select.limit(limit.pop(), copy=False) 1520 if order: 1521 select = select.order_by(order.pop(), copy=False) 1522 return self.sql(select) 1523 1524 sqls: t.List[str] = [] 1525 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1526 1527 while stack: 1528 node = stack.pop() 1529 1530 if isinstance(node, exp.SetOperation): 1531 stack.append(node.expression) 1532 stack.append( 1533 self.maybe_comment( 1534 self.set_operation(node), comments=node.comments, separated=True 1535 ) 1536 ) 1537 stack.append(node.this) 1538 else: 1539 sqls.append(self.sql(node)) 1540 1541 this = self.sep().join(sqls) 1542 this = self.query_modifiers(expression, this) 1543 return self.prepend_ctes(expression, this)
1545 def fetch_sql(self, expression: exp.Fetch) -> str: 1546 direction = expression.args.get("direction") 1547 direction = f" {direction}" if direction else "" 1548 count = self.sql(expression, "count") 1549 count = f" {count}" if count else "" 1550 limit_options = self.sql(expression, "limit_options") 1551 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1552 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1554 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1555 percent = " PERCENT" if expression.args.get("percent") else "" 1556 rows = " ROWS" if expression.args.get("rows") else "" 1557 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1558 if not with_ties and rows: 1559 with_ties = " ONLY" 1560 return f"{percent}{rows}{with_ties}"
1562 def filter_sql(self, expression: exp.Filter) -> str: 1563 if self.AGGREGATE_FILTER_SUPPORTED: 1564 this = self.sql(expression, "this") 1565 where = self.sql(expression, "expression").strip() 1566 return f"{this} FILTER({where})" 1567 1568 agg = expression.this 1569 agg_arg = agg.this 1570 cond = expression.expression.this 1571 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1572 return self.sql(agg)
1581 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1582 using = self.sql(expression, "using") 1583 using = f" USING {using}" if using else "" 1584 columns = self.expressions(expression, key="columns", flat=True) 1585 columns = f"({columns})" if columns else "" 1586 partition_by = self.expressions(expression, key="partition_by", flat=True) 1587 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1588 where = self.sql(expression, "where") 1589 include = self.expressions(expression, key="include", flat=True) 1590 if include: 1591 include = f" INCLUDE ({include})" 1592 with_storage = self.expressions(expression, key="with_storage", flat=True) 1593 with_storage = f" WITH ({with_storage})" if with_storage else "" 1594 tablespace = self.sql(expression, "tablespace") 1595 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1596 on = self.sql(expression, "on") 1597 on = f" ON {on}" if on else "" 1598 1599 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1601 def index_sql(self, expression: exp.Index) -> str: 1602 unique = "UNIQUE " if expression.args.get("unique") else "" 1603 primary = "PRIMARY " if expression.args.get("primary") else "" 1604 amp = "AMP " if expression.args.get("amp") else "" 1605 name = self.sql(expression, "this") 1606 name = f"{name} " if name else "" 1607 table = self.sql(expression, "table") 1608 table = f"{self.INDEX_ON} {table}" if table else "" 1609 1610 index = "INDEX " if not table else "" 1611 1612 params = self.sql(expression, "params") 1613 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1615 def identifier_sql(self, expression: exp.Identifier) -> str: 1616 text = expression.name 1617 lower = text.lower() 1618 text = lower if self.normalize and not expression.quoted else text 1619 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1620 if ( 1621 expression.quoted 1622 or self.dialect.can_identify(text, self.identify) 1623 or lower in self.RESERVED_KEYWORDS 1624 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1625 ): 1626 text = f"{self._identifier_start}{text}{self._identifier_end}" 1627 return text
1642 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1643 input_format = self.sql(expression, "input_format") 1644 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1645 output_format = self.sql(expression, "output_format") 1646 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1647 return self.sep().join((input_format, output_format))
1657 def properties_sql(self, expression: exp.Properties) -> str: 1658 root_properties = [] 1659 with_properties = [] 1660 1661 for p in expression.expressions: 1662 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1663 if p_loc == exp.Properties.Location.POST_WITH: 1664 with_properties.append(p) 1665 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1666 root_properties.append(p) 1667 1668 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1669 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1670 1671 if root_props and with_props and not self.pretty: 1672 with_props = " " + with_props 1673 1674 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1681 def properties( 1682 self, 1683 properties: exp.Properties, 1684 prefix: str = "", 1685 sep: str = ", ", 1686 suffix: str = "", 1687 wrapped: bool = True, 1688 ) -> str: 1689 if properties.expressions: 1690 expressions = self.expressions(properties, sep=sep, indent=False) 1691 if expressions: 1692 expressions = self.wrap(expressions) if wrapped else expressions 1693 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1694 return ""
1699 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1700 properties_locs = defaultdict(list) 1701 for p in properties.expressions: 1702 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1703 if p_loc != exp.Properties.Location.UNSUPPORTED: 1704 properties_locs[p_loc].append(p) 1705 else: 1706 self.unsupported(f"Unsupported property {p.key}") 1707 1708 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1715 def property_sql(self, expression: exp.Property) -> str: 1716 property_cls = expression.__class__ 1717 if property_cls == exp.Property: 1718 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1719 1720 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1721 if not property_name: 1722 self.unsupported(f"Unsupported property {expression.key}") 1723 1724 return f"{property_name}={self.sql(expression, 'this')}"
1726 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1727 if self.SUPPORTS_CREATE_TABLE_LIKE: 1728 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1729 options = f" {options}" if options else "" 1730 1731 like = f"LIKE {self.sql(expression, 'this')}{options}" 1732 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1733 like = f"({like})" 1734 1735 return like 1736 1737 if expression.expressions: 1738 self.unsupported("Transpilation of LIKE property options is unsupported") 1739 1740 select = exp.select("*").from_(expression.this).limit(0) 1741 return f"AS {self.sql(select)}"
1748 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1749 no = "NO " if expression.args.get("no") else "" 1750 local = expression.args.get("local") 1751 local = f"{local} " if local else "" 1752 dual = "DUAL " if expression.args.get("dual") else "" 1753 before = "BEFORE " if expression.args.get("before") else "" 1754 after = "AFTER " if expression.args.get("after") else "" 1755 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1771 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1772 if expression.args.get("no"): 1773 return "NO MERGEBLOCKRATIO" 1774 if expression.args.get("default"): 1775 return "DEFAULT MERGEBLOCKRATIO" 1776 1777 percent = " PERCENT" if expression.args.get("percent") else "" 1778 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1780 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1781 default = expression.args.get("default") 1782 minimum = expression.args.get("minimum") 1783 maximum = expression.args.get("maximum") 1784 if default or minimum or maximum: 1785 if default: 1786 prop = "DEFAULT" 1787 elif minimum: 1788 prop = "MINIMUM" 1789 else: 1790 prop = "MAXIMUM" 1791 return f"{prop} DATABLOCKSIZE" 1792 units = expression.args.get("units") 1793 units = f" {units}" if units else "" 1794 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1796 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1797 autotemp = expression.args.get("autotemp") 1798 always = expression.args.get("always") 1799 default = expression.args.get("default") 1800 manual = expression.args.get("manual") 1801 never = expression.args.get("never") 1802 1803 if autotemp is not None: 1804 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1805 elif always: 1806 prop = "ALWAYS" 1807 elif default: 1808 prop = "DEFAULT" 1809 elif manual: 1810 prop = "MANUAL" 1811 elif never: 1812 prop = "NEVER" 1813 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1815 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1816 no = expression.args.get("no") 1817 no = " NO" if no else "" 1818 concurrent = expression.args.get("concurrent") 1819 concurrent = " CONCURRENT" if concurrent else "" 1820 target = self.sql(expression, "target") 1821 target = f" {target}" if target else "" 1822 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1824 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1825 if isinstance(expression.this, list): 1826 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1827 if expression.this: 1828 modulus = self.sql(expression, "this") 1829 remainder = self.sql(expression, "expression") 1830 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1831 1832 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1833 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1834 return f"FROM ({from_expressions}) TO ({to_expressions})"
1836 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1837 this = self.sql(expression, "this") 1838 1839 for_values_or_default = expression.expression 1840 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1841 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1842 else: 1843 for_values_or_default = " DEFAULT" 1844 1845 return f"PARTITION OF {this}{for_values_or_default}"
1847 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1848 kind = expression.args.get("kind") 1849 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1850 for_or_in = expression.args.get("for_or_in") 1851 for_or_in = f" {for_or_in}" if for_or_in else "" 1852 lock_type = expression.args.get("lock_type") 1853 override = " OVERRIDE" if expression.args.get("override") else "" 1854 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1856 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1857 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1858 statistics = expression.args.get("statistics") 1859 statistics_sql = "" 1860 if statistics is not None: 1861 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1862 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1864 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1865 this = self.sql(expression, "this") 1866 this = f"HISTORY_TABLE={this}" if this else "" 1867 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1868 data_consistency = ( 1869 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1870 ) 1871 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1872 retention_period = ( 1873 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1874 ) 1875 1876 if this: 1877 on_sql = self.func("ON", this, data_consistency, retention_period) 1878 else: 1879 on_sql = "ON" if expression.args.get("on") else "OFF" 1880 1881 sql = f"SYSTEM_VERSIONING={on_sql}" 1882 1883 return f"WITH({sql})" if expression.args.get("with") else sql
1885 def insert_sql(self, expression: exp.Insert) -> str: 1886 hint = self.sql(expression, "hint") 1887 overwrite = expression.args.get("overwrite") 1888 1889 if isinstance(expression.this, exp.Directory): 1890 this = " OVERWRITE" if overwrite else " INTO" 1891 else: 1892 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1893 1894 stored = self.sql(expression, "stored") 1895 stored = f" {stored}" if stored else "" 1896 alternative = expression.args.get("alternative") 1897 alternative = f" OR {alternative}" if alternative else "" 1898 ignore = " IGNORE" if expression.args.get("ignore") else "" 1899 is_function = expression.args.get("is_function") 1900 if is_function: 1901 this = f"{this} FUNCTION" 1902 this = f"{this} {self.sql(expression, 'this')}" 1903 1904 exists = " IF EXISTS" if expression.args.get("exists") else "" 1905 where = self.sql(expression, "where") 1906 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1907 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1908 on_conflict = self.sql(expression, "conflict") 1909 on_conflict = f" {on_conflict}" if on_conflict else "" 1910 by_name = " BY NAME" if expression.args.get("by_name") else "" 1911 returning = self.sql(expression, "returning") 1912 1913 if self.RETURNING_END: 1914 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1915 else: 1916 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1917 1918 partition_by = self.sql(expression, "partition") 1919 partition_by = f" {partition_by}" if partition_by else "" 1920 settings = self.sql(expression, "settings") 1921 settings = f" {settings}" if settings else "" 1922 1923 source = self.sql(expression, "source") 1924 source = f"TABLE {source}" if source else "" 1925 1926 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1927 return self.prepend_ctes(expression, sql)
1945 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1946 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1947 1948 constraint = self.sql(expression, "constraint") 1949 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1950 1951 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1952 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1953 action = self.sql(expression, "action") 1954 1955 expressions = self.expressions(expression, flat=True) 1956 if expressions: 1957 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1958 expressions = f" {set_keyword}{expressions}" 1959 1960 where = self.sql(expression, "where") 1961 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1966 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1967 fields = self.sql(expression, "fields") 1968 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1969 escaped = self.sql(expression, "escaped") 1970 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1971 items = self.sql(expression, "collection_items") 1972 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1973 keys = self.sql(expression, "map_keys") 1974 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1975 lines = self.sql(expression, "lines") 1976 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1977 null = self.sql(expression, "null") 1978 null = f" NULL DEFINED AS {null}" if null else "" 1979 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
2007 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 2008 table = self.table_parts(expression) 2009 only = "ONLY " if expression.args.get("only") else "" 2010 partition = self.sql(expression, "partition") 2011 partition = f" {partition}" if partition else "" 2012 version = self.sql(expression, "version") 2013 version = f" {version}" if version else "" 2014 alias = self.sql(expression, "alias") 2015 alias = f"{sep}{alias}" if alias else "" 2016 2017 sample = self.sql(expression, "sample") 2018 if self.dialect.ALIAS_POST_TABLESAMPLE: 2019 sample_pre_alias = sample 2020 sample_post_alias = "" 2021 else: 2022 sample_pre_alias = "" 2023 sample_post_alias = sample 2024 2025 hints = self.expressions(expression, key="hints", sep=" ") 2026 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2027 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2028 joins = self.indent( 2029 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2030 ) 2031 laterals = self.expressions(expression, key="laterals", sep="") 2032 2033 file_format = self.sql(expression, "format") 2034 if file_format: 2035 pattern = self.sql(expression, "pattern") 2036 pattern = f", PATTERN => {pattern}" if pattern else "" 2037 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2038 2039 ordinality = expression.args.get("ordinality") or "" 2040 if ordinality: 2041 ordinality = f" WITH ORDINALITY{alias}" 2042 alias = "" 2043 2044 when = self.sql(expression, "when") 2045 if when: 2046 table = f"{table} {when}" 2047 2048 changes = self.sql(expression, "changes") 2049 changes = f" {changes}" if changes else "" 2050 2051 rows_from = self.expressions(expression, key="rows_from") 2052 if rows_from: 2053 table = f"ROWS FROM {self.wrap(rows_from)}" 2054 2055 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2057 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2058 table = self.func("TABLE", expression.this) 2059 alias = self.sql(expression, "alias") 2060 alias = f" AS {alias}" if alias else "" 2061 sample = self.sql(expression, "sample") 2062 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2063 joins = self.indent( 2064 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2065 ) 2066 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2068 def tablesample_sql( 2069 self, 2070 expression: exp.TableSample, 2071 tablesample_keyword: t.Optional[str] = None, 2072 ) -> str: 2073 method = self.sql(expression, "method") 2074 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2075 numerator = self.sql(expression, "bucket_numerator") 2076 denominator = self.sql(expression, "bucket_denominator") 2077 field = self.sql(expression, "bucket_field") 2078 field = f" ON {field}" if field else "" 2079 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2080 seed = self.sql(expression, "seed") 2081 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2082 2083 size = self.sql(expression, "size") 2084 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2085 size = f"{size} ROWS" 2086 2087 percent = self.sql(expression, "percent") 2088 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2089 percent = f"{percent} PERCENT" 2090 2091 expr = f"{bucket}{percent}{size}" 2092 if self.TABLESAMPLE_REQUIRES_PARENS: 2093 expr = f"({expr})" 2094 2095 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2097 def pivot_sql(self, expression: exp.Pivot) -> str: 2098 expressions = self.expressions(expression, flat=True) 2099 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2100 2101 group = self.sql(expression, "group") 2102 2103 if expression.this: 2104 this = self.sql(expression, "this") 2105 if not expressions: 2106 return f"UNPIVOT {this}" 2107 2108 on = f"{self.seg('ON')} {expressions}" 2109 into = self.sql(expression, "into") 2110 into = f"{self.seg('INTO')} {into}" if into else "" 2111 using = self.expressions(expression, key="using", flat=True) 2112 using = f"{self.seg('USING')} {using}" if using else "" 2113 return f"{direction} {this}{on}{into}{using}{group}" 2114 2115 alias = self.sql(expression, "alias") 2116 alias = f" AS {alias}" if alias else "" 2117 2118 fields = self.expressions( 2119 expression, 2120 "fields", 2121 sep=" ", 2122 dynamic=True, 2123 new_line=True, 2124 skip_first=True, 2125 skip_last=True, 2126 ) 2127 2128 include_nulls = expression.args.get("include_nulls") 2129 if include_nulls is not None: 2130 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2131 else: 2132 nulls = "" 2133 2134 default_on_null = self.sql(expression, "default_on_null") 2135 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2136 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2147 def update_sql(self, expression: exp.Update) -> str: 2148 this = self.sql(expression, "this") 2149 set_sql = self.expressions(expression, flat=True) 2150 from_sql = self.sql(expression, "from") 2151 where_sql = self.sql(expression, "where") 2152 returning = self.sql(expression, "returning") 2153 order = self.sql(expression, "order") 2154 limit = self.sql(expression, "limit") 2155 if self.RETURNING_END: 2156 expression_sql = f"{from_sql}{where_sql}{returning}" 2157 else: 2158 expression_sql = f"{returning}{from_sql}{where_sql}" 2159 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2160 return self.prepend_ctes(expression, sql)
2162 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2163 values_as_table = values_as_table and self.VALUES_AS_TABLE 2164 2165 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2166 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2167 args = self.expressions(expression) 2168 alias = self.sql(expression, "alias") 2169 values = f"VALUES{self.seg('')}{args}" 2170 values = ( 2171 f"({values})" 2172 if self.WRAP_DERIVED_VALUES 2173 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2174 else values 2175 ) 2176 return f"{values} AS {alias}" if alias else values 2177 2178 # Converts `VALUES...` expression into a series of select unions. 2179 alias_node = expression.args.get("alias") 2180 column_names = alias_node and alias_node.columns 2181 2182 selects: t.List[exp.Query] = [] 2183 2184 for i, tup in enumerate(expression.expressions): 2185 row = tup.expressions 2186 2187 if i == 0 and column_names: 2188 row = [ 2189 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2190 ] 2191 2192 selects.append(exp.Select(expressions=row)) 2193 2194 if self.pretty: 2195 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2196 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2197 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2198 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2199 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2200 2201 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2202 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2203 return f"({unions}){alias}"
2208 @unsupported_args("expressions") 2209 def into_sql(self, expression: exp.Into) -> str: 2210 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2211 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2212 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2229 def group_sql(self, expression: exp.Group) -> str: 2230 group_by_all = expression.args.get("all") 2231 if group_by_all is True: 2232 modifier = " ALL" 2233 elif group_by_all is False: 2234 modifier = " DISTINCT" 2235 else: 2236 modifier = "" 2237 2238 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2239 2240 grouping_sets = self.expressions(expression, key="grouping_sets") 2241 cube = self.expressions(expression, key="cube") 2242 rollup = self.expressions(expression, key="rollup") 2243 2244 groupings = csv( 2245 self.seg(grouping_sets) if grouping_sets else "", 2246 self.seg(cube) if cube else "", 2247 self.seg(rollup) if rollup else "", 2248 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2249 sep=self.GROUPINGS_SEP, 2250 ) 2251 2252 if ( 2253 expression.expressions 2254 and groupings 2255 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2256 ): 2257 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2258 2259 return f"{group_by}{groupings}"
2265 def connect_sql(self, expression: exp.Connect) -> str: 2266 start = self.sql(expression, "start") 2267 start = self.seg(f"START WITH {start}") if start else "" 2268 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2269 connect = self.sql(expression, "connect") 2270 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2271 return start + connect
2276 def join_sql(self, expression: exp.Join) -> str: 2277 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2278 side = None 2279 else: 2280 side = expression.side 2281 2282 op_sql = " ".join( 2283 op 2284 for op in ( 2285 expression.method, 2286 "GLOBAL" if expression.args.get("global") else None, 2287 side, 2288 expression.kind, 2289 expression.hint if self.JOIN_HINTS else None, 2290 ) 2291 if op 2292 ) 2293 match_cond = self.sql(expression, "match_condition") 2294 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2295 on_sql = self.sql(expression, "on") 2296 using = expression.args.get("using") 2297 2298 if not on_sql and using: 2299 on_sql = csv(*(self.sql(column) for column in using)) 2300 2301 this = expression.this 2302 this_sql = self.sql(this) 2303 2304 exprs = self.expressions(expression) 2305 if exprs: 2306 this_sql = f"{this_sql},{self.seg(exprs)}" 2307 2308 if on_sql: 2309 on_sql = self.indent(on_sql, skip_first=True) 2310 space = self.seg(" " * self.pad) if self.pretty else " " 2311 if using: 2312 on_sql = f"{space}USING ({on_sql})" 2313 else: 2314 on_sql = f"{space}ON {on_sql}" 2315 elif not op_sql: 2316 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2317 return f" {this_sql}" 2318 2319 return f", {this_sql}" 2320 2321 if op_sql != "STRAIGHT_JOIN": 2322 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2323 2324 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2325 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
def
lambda_sql( self, expression: sqlglot.expressions.Lambda, arrow_sep: str = '->', wrap: bool = True) -> str:
2332 def lateral_op(self, expression: exp.Lateral) -> str: 2333 cross_apply = expression.args.get("cross_apply") 2334 2335 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2336 if cross_apply is True: 2337 op = "INNER JOIN " 2338 elif cross_apply is False: 2339 op = "LEFT JOIN " 2340 else: 2341 op = "" 2342 2343 return f"{op}LATERAL"
2345 def lateral_sql(self, expression: exp.Lateral) -> str: 2346 this = self.sql(expression, "this") 2347 2348 if expression.args.get("view"): 2349 alias = expression.args["alias"] 2350 columns = self.expressions(alias, key="columns", flat=True) 2351 table = f" {alias.name}" if alias.name else "" 2352 columns = f" AS {columns}" if columns else "" 2353 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2354 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2355 2356 alias = self.sql(expression, "alias") 2357 alias = f" AS {alias}" if alias else "" 2358 2359 ordinality = expression.args.get("ordinality") or "" 2360 if ordinality: 2361 ordinality = f" WITH ORDINALITY{alias}" 2362 alias = "" 2363 2364 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2366 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2367 this = self.sql(expression, "this") 2368 2369 args = [ 2370 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2371 for e in (expression.args.get(k) for k in ("offset", "expression")) 2372 if e 2373 ] 2374 2375 args_sql = ", ".join(self.sql(e) for e in args) 2376 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2377 expressions = self.expressions(expression, flat=True) 2378 limit_options = self.sql(expression, "limit_options") 2379 expressions = f" BY {expressions}" if expressions else "" 2380 2381 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2383 def offset_sql(self, expression: exp.Offset) -> str: 2384 this = self.sql(expression, "this") 2385 value = expression.expression 2386 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2387 expressions = self.expressions(expression, flat=True) 2388 expressions = f" BY {expressions}" if expressions else "" 2389 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2391 def setitem_sql(self, expression: exp.SetItem) -> str: 2392 kind = self.sql(expression, "kind") 2393 kind = f"{kind} " if kind else "" 2394 this = self.sql(expression, "this") 2395 expressions = self.expressions(expression) 2396 collate = self.sql(expression, "collate") 2397 collate = f" COLLATE {collate}" if collate else "" 2398 global_ = "GLOBAL " if expression.args.get("global") else "" 2399 return f"{global_}{kind}{this}{expressions}{collate}"
2409 def lock_sql(self, expression: exp.Lock) -> str: 2410 if not self.LOCKING_READS_SUPPORTED: 2411 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2412 return "" 2413 2414 update = expression.args["update"] 2415 key = expression.args.get("key") 2416 if update: 2417 lock_type = "FOR NO KEY UPDATE" if key else "FOR UPDATE" 2418 else: 2419 lock_type = "FOR KEY SHARE" if key else "FOR SHARE" 2420 expressions = self.expressions(expression, flat=True) 2421 expressions = f" OF {expressions}" if expressions else "" 2422 wait = expression.args.get("wait") 2423 2424 if wait is not None: 2425 if isinstance(wait, exp.Literal): 2426 wait = f" WAIT {self.sql(wait)}" 2427 else: 2428 wait = " NOWAIT" if wait else " SKIP LOCKED" 2429 2430 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2438 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2439 if self.dialect.ESCAPED_SEQUENCES: 2440 to_escaped = self.dialect.ESCAPED_SEQUENCES 2441 text = "".join( 2442 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2443 ) 2444 2445 return self._replace_line_breaks(text).replace( 2446 self.dialect.QUOTE_END, self._escaped_quote_end 2447 )
2449 def loaddata_sql(self, expression: exp.LoadData) -> str: 2450 local = " LOCAL" if expression.args.get("local") else "" 2451 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2452 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2453 this = f" INTO TABLE {self.sql(expression, 'this')}" 2454 partition = self.sql(expression, "partition") 2455 partition = f" {partition}" if partition else "" 2456 input_format = self.sql(expression, "input_format") 2457 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2458 serde = self.sql(expression, "serde") 2459 serde = f" SERDE {serde}" if serde else "" 2460 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2468 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2469 this = self.sql(expression, "this") 2470 this = f"{this} " if this else this 2471 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2472 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2474 def withfill_sql(self, expression: exp.WithFill) -> str: 2475 from_sql = self.sql(expression, "from") 2476 from_sql = f" FROM {from_sql}" if from_sql else "" 2477 to_sql = self.sql(expression, "to") 2478 to_sql = f" TO {to_sql}" if to_sql else "" 2479 step_sql = self.sql(expression, "step") 2480 step_sql = f" STEP {step_sql}" if step_sql else "" 2481 interpolated_values = [ 2482 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2483 if isinstance(e, exp.Alias) 2484 else self.sql(e, "this") 2485 for e in expression.args.get("interpolate") or [] 2486 ] 2487 interpolate = ( 2488 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2489 ) 2490 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2501 def ordered_sql(self, expression: exp.Ordered) -> str: 2502 desc = expression.args.get("desc") 2503 asc = not desc 2504 2505 nulls_first = expression.args.get("nulls_first") 2506 nulls_last = not nulls_first 2507 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2508 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2509 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2510 2511 this = self.sql(expression, "this") 2512 2513 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2514 nulls_sort_change = "" 2515 if nulls_first and ( 2516 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2517 ): 2518 nulls_sort_change = " NULLS FIRST" 2519 elif ( 2520 nulls_last 2521 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2522 and not nulls_are_last 2523 ): 2524 nulls_sort_change = " NULLS LAST" 2525 2526 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2527 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2528 window = expression.find_ancestor(exp.Window, exp.Select) 2529 if isinstance(window, exp.Window) and window.args.get("spec"): 2530 self.unsupported( 2531 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2532 ) 2533 nulls_sort_change = "" 2534 elif self.NULL_ORDERING_SUPPORTED is False and ( 2535 (asc and nulls_sort_change == " NULLS LAST") 2536 or (desc and nulls_sort_change == " NULLS FIRST") 2537 ): 2538 # BigQuery does not allow these ordering/nulls combinations when used under 2539 # an aggregation func or under a window containing one 2540 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2541 2542 if isinstance(ancestor, exp.Window): 2543 ancestor = ancestor.this 2544 if isinstance(ancestor, exp.AggFunc): 2545 self.unsupported( 2546 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2547 ) 2548 nulls_sort_change = "" 2549 elif self.NULL_ORDERING_SUPPORTED is None: 2550 if expression.this.is_int: 2551 self.unsupported( 2552 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2553 ) 2554 elif not isinstance(expression.this, exp.Rand): 2555 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2556 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2557 nulls_sort_change = "" 2558 2559 with_fill = self.sql(expression, "with_fill") 2560 with_fill = f" {with_fill}" if with_fill else "" 2561 2562 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2572 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2573 partition = self.partition_by_sql(expression) 2574 order = self.sql(expression, "order") 2575 measures = self.expressions(expression, key="measures") 2576 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2577 rows = self.sql(expression, "rows") 2578 rows = self.seg(rows) if rows else "" 2579 after = self.sql(expression, "after") 2580 after = self.seg(after) if after else "" 2581 pattern = self.sql(expression, "pattern") 2582 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2583 definition_sqls = [ 2584 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2585 for definition in expression.args.get("define", []) 2586 ] 2587 definitions = self.expressions(sqls=definition_sqls) 2588 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2589 body = "".join( 2590 ( 2591 partition, 2592 order, 2593 measures, 2594 rows, 2595 after, 2596 pattern, 2597 define, 2598 ) 2599 ) 2600 alias = self.sql(expression, "alias") 2601 alias = f" {alias}" if alias else "" 2602 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2604 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2605 limit = expression.args.get("limit") 2606 2607 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2608 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2609 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2610 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2611 2612 return csv( 2613 *sqls, 2614 *[self.sql(join) for join in expression.args.get("joins") or []], 2615 self.sql(expression, "match"), 2616 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2617 self.sql(expression, "prewhere"), 2618 self.sql(expression, "where"), 2619 self.sql(expression, "connect"), 2620 self.sql(expression, "group"), 2621 self.sql(expression, "having"), 2622 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2623 self.sql(expression, "order"), 2624 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2625 *self.after_limit_modifiers(expression), 2626 self.options_modifier(expression), 2627 self.for_modifiers(expression), 2628 sep="", 2629 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2643 def offset_limit_modifiers( 2644 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2645 ) -> t.List[str]: 2646 return [ 2647 self.sql(expression, "offset") if fetch else self.sql(limit), 2648 self.sql(limit) if fetch else self.sql(expression, "offset"), 2649 ]
2656 def select_sql(self, expression: exp.Select) -> str: 2657 into = expression.args.get("into") 2658 if not self.SUPPORTS_SELECT_INTO and into: 2659 into.pop() 2660 2661 hint = self.sql(expression, "hint") 2662 distinct = self.sql(expression, "distinct") 2663 distinct = f" {distinct}" if distinct else "" 2664 kind = self.sql(expression, "kind") 2665 2666 limit = expression.args.get("limit") 2667 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2668 top = self.limit_sql(limit, top=True) 2669 limit.pop() 2670 else: 2671 top = "" 2672 2673 expressions = self.expressions(expression) 2674 2675 if kind: 2676 if kind in self.SELECT_KINDS: 2677 kind = f" AS {kind}" 2678 else: 2679 if kind == "STRUCT": 2680 expressions = self.expressions( 2681 sqls=[ 2682 self.sql( 2683 exp.Struct( 2684 expressions=[ 2685 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2686 if isinstance(e, exp.Alias) 2687 else e 2688 for e in expression.expressions 2689 ] 2690 ) 2691 ) 2692 ] 2693 ) 2694 kind = "" 2695 2696 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2697 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2698 2699 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2700 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2701 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2702 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2703 sql = self.query_modifiers( 2704 expression, 2705 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2706 self.sql(expression, "into", comment=False), 2707 self.sql(expression, "from", comment=False), 2708 ) 2709 2710 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2711 if expression.args.get("with"): 2712 sql = self.maybe_comment(sql, expression) 2713 expression.pop_comments() 2714 2715 sql = self.prepend_ctes(expression, sql) 2716 2717 if not self.SUPPORTS_SELECT_INTO and into: 2718 if into.args.get("temporary"): 2719 table_kind = " TEMPORARY" 2720 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2721 table_kind = " UNLOGGED" 2722 else: 2723 table_kind = "" 2724 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2725 2726 return sql
2738 def star_sql(self, expression: exp.Star) -> str: 2739 except_ = self.expressions(expression, key="except", flat=True) 2740 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2741 replace = self.expressions(expression, key="replace", flat=True) 2742 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2743 rename = self.expressions(expression, key="rename", flat=True) 2744 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2745 return f"*{except_}{replace}{rename}"
2761 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2762 alias = self.sql(expression, "alias") 2763 alias = f"{sep}{alias}" if alias else "" 2764 sample = self.sql(expression, "sample") 2765 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2766 alias = f"{sample}{alias}" 2767 2768 # Set to None so it's not generated again by self.query_modifiers() 2769 expression.set("sample", None) 2770 2771 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2772 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2773 return self.prepend_ctes(expression, sql)
2779 def unnest_sql(self, expression: exp.Unnest) -> str: 2780 args = self.expressions(expression, flat=True) 2781 2782 alias = expression.args.get("alias") 2783 offset = expression.args.get("offset") 2784 2785 if self.UNNEST_WITH_ORDINALITY: 2786 if alias and isinstance(offset, exp.Expression): 2787 alias.append("columns", offset) 2788 2789 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2790 columns = alias.columns 2791 alias = self.sql(columns[0]) if columns else "" 2792 else: 2793 alias = self.sql(alias) 2794 2795 alias = f" AS {alias}" if alias else alias 2796 if self.UNNEST_WITH_ORDINALITY: 2797 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2798 else: 2799 if isinstance(offset, exp.Expression): 2800 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2801 elif offset: 2802 suffix = f"{alias} WITH OFFSET" 2803 else: 2804 suffix = alias 2805 2806 return f"UNNEST({args}){suffix}"
2815 def window_sql(self, expression: exp.Window) -> str: 2816 this = self.sql(expression, "this") 2817 partition = self.partition_by_sql(expression) 2818 order = expression.args.get("order") 2819 order = self.order_sql(order, flat=True) if order else "" 2820 spec = self.sql(expression, "spec") 2821 alias = self.sql(expression, "alias") 2822 over = self.sql(expression, "over") or "OVER" 2823 2824 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2825 2826 first = expression.args.get("first") 2827 if first is None: 2828 first = "" 2829 else: 2830 first = "FIRST" if first else "LAST" 2831 2832 if not partition and not order and not spec and alias: 2833 return f"{this} {alias}" 2834 2835 args = self.format_args( 2836 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2837 ) 2838 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2844 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2845 kind = self.sql(expression, "kind") 2846 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2847 end = ( 2848 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2849 or "CURRENT ROW" 2850 ) 2851 2852 window_spec = f"{kind} BETWEEN {start} AND {end}" 2853 2854 exclude = self.sql(expression, "exclude") 2855 if exclude: 2856 if self.SUPPORTS_WINDOW_EXCLUDE: 2857 window_spec += f" EXCLUDE {exclude}" 2858 else: 2859 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2860 2861 return window_spec
2868 def between_sql(self, expression: exp.Between) -> str: 2869 this = self.sql(expression, "this") 2870 low = self.sql(expression, "low") 2871 high = self.sql(expression, "high") 2872 symmetric = expression.args.get("symmetric") 2873 2874 if symmetric and not self.SUPPORTS_BETWEEN_FLAGS: 2875 return f"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})" 2876 2877 flag = ( 2878 " SYMMETRIC" 2879 if symmetric 2880 else " ASYMMETRIC" 2881 if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS 2882 else "" # silently drop ASYMMETRIC – semantics identical 2883 ) 2884 return f"{this} BETWEEN{flag} {low} AND {high}"
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2886 def bracket_offset_expressions( 2887 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2888 ) -> t.List[exp.Expression]: 2889 return apply_index_offset( 2890 expression.this, 2891 expression.expressions, 2892 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2893 dialect=self.dialect, 2894 )
2904 def any_sql(self, expression: exp.Any) -> str: 2905 this = self.sql(expression, "this") 2906 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2907 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2908 this = self.wrap(this) 2909 return f"ANY{this}" 2910 return f"ANY {this}"
2915 def case_sql(self, expression: exp.Case) -> str: 2916 this = self.sql(expression, "this") 2917 statements = [f"CASE {this}" if this else "CASE"] 2918 2919 for e in expression.args["ifs"]: 2920 statements.append(f"WHEN {self.sql(e, 'this')}") 2921 statements.append(f"THEN {self.sql(e, 'true')}") 2922 2923 default = self.sql(expression, "default") 2924 2925 if default: 2926 statements.append(f"ELSE {default}") 2927 2928 statements.append("END") 2929 2930 if self.pretty and self.too_wide(statements): 2931 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2932 2933 return " ".join(statements)
2945 def extract_sql(self, expression: exp.Extract) -> str: 2946 from sqlglot.dialects.dialect import map_date_part 2947 2948 this = ( 2949 map_date_part(expression.this, self.dialect) 2950 if self.NORMALIZE_EXTRACT_DATE_PARTS 2951 else expression.this 2952 ) 2953 this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name 2954 expression_sql = self.sql(expression, "expression") 2955 2956 return f"EXTRACT({this_sql} FROM {expression_sql})"
2958 def trim_sql(self, expression: exp.Trim) -> str: 2959 trim_type = self.sql(expression, "position") 2960 2961 if trim_type == "LEADING": 2962 func_name = "LTRIM" 2963 elif trim_type == "TRAILING": 2964 func_name = "RTRIM" 2965 else: 2966 func_name = "TRIM" 2967 2968 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
2970 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2971 args = expression.expressions 2972 if isinstance(expression, exp.ConcatWs): 2973 args = args[1:] # Skip the delimiter 2974 2975 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2976 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2977 2978 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2979 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2980 2981 return args
2983 def concat_sql(self, expression: exp.Concat) -> str: 2984 expressions = self.convert_concat_args(expression) 2985 2986 # Some dialects don't allow a single-argument CONCAT call 2987 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2988 return self.sql(expressions[0]) 2989 2990 return self.func("CONCAT", *expressions)
3001 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 3002 expressions = self.expressions(expression, flat=True) 3003 expressions = f" ({expressions})" if expressions else "" 3004 reference = self.sql(expression, "reference") 3005 reference = f" {reference}" if reference else "" 3006 delete = self.sql(expression, "delete") 3007 delete = f" ON DELETE {delete}" if delete else "" 3008 update = self.sql(expression, "update") 3009 update = f" ON UPDATE {update}" if update else "" 3010 options = self.expressions(expression, key="options", flat=True, sep=" ") 3011 options = f" {options}" if options else "" 3012 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
3014 def primarykey_sql(self, expression: exp.PrimaryKey) -> str: 3015 expressions = self.expressions(expression, flat=True) 3016 include = self.sql(expression, "include") 3017 options = self.expressions(expression, key="options", flat=True, sep=" ") 3018 options = f" {options}" if options else "" 3019 return f"PRIMARY KEY ({expressions}){include}{options}"
3032 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 3033 path = self.expressions(expression, sep="", flat=True).lstrip(".") 3034 3035 if expression.args.get("escape"): 3036 path = self.escape_str(path) 3037 3038 if self.QUOTE_JSON_PATH: 3039 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 3040 3041 return path
3043 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 3044 if isinstance(expression, exp.JSONPathPart): 3045 transform = self.TRANSFORMS.get(expression.__class__) 3046 if not callable(transform): 3047 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 3048 return "" 3049 3050 return transform(self, expression) 3051 3052 if isinstance(expression, int): 3053 return str(expression) 3054 3055 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3056 escaped = expression.replace("'", "\\'") 3057 escaped = f"\\'{expression}\\'" 3058 else: 3059 escaped = expression.replace('"', '\\"') 3060 escaped = f'"{escaped}"' 3061 3062 return escaped
3067 def formatphrase_sql(self, expression: exp.FormatPhrase) -> str: 3068 # Output the Teradata column FORMAT override. 3069 # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT 3070 this = self.sql(expression, "this") 3071 fmt = self.sql(expression, "format") 3072 return f"{this} (FORMAT {fmt})"
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3074 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3075 null_handling = expression.args.get("null_handling") 3076 null_handling = f" {null_handling}" if null_handling else "" 3077 3078 unique_keys = expression.args.get("unique_keys") 3079 if unique_keys is not None: 3080 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3081 else: 3082 unique_keys = "" 3083 3084 return_type = self.sql(expression, "return_type") 3085 return_type = f" RETURNING {return_type}" if return_type else "" 3086 encoding = self.sql(expression, "encoding") 3087 encoding = f" ENCODING {encoding}" if encoding else "" 3088 3089 return self.func( 3090 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3091 *expression.expressions, 3092 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3093 )
3098 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3099 null_handling = expression.args.get("null_handling") 3100 null_handling = f" {null_handling}" if null_handling else "" 3101 return_type = self.sql(expression, "return_type") 3102 return_type = f" RETURNING {return_type}" if return_type else "" 3103 strict = " STRICT" if expression.args.get("strict") else "" 3104 return self.func( 3105 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3106 )
3108 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3109 this = self.sql(expression, "this") 3110 order = self.sql(expression, "order") 3111 null_handling = expression.args.get("null_handling") 3112 null_handling = f" {null_handling}" if null_handling else "" 3113 return_type = self.sql(expression, "return_type") 3114 return_type = f" RETURNING {return_type}" if return_type else "" 3115 strict = " STRICT" if expression.args.get("strict") else "" 3116 return self.func( 3117 "JSON_ARRAYAGG", 3118 this, 3119 suffix=f"{order}{null_handling}{return_type}{strict})", 3120 )
3122 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3123 path = self.sql(expression, "path") 3124 path = f" PATH {path}" if path else "" 3125 nested_schema = self.sql(expression, "nested_schema") 3126 3127 if nested_schema: 3128 return f"NESTED{path} {nested_schema}" 3129 3130 this = self.sql(expression, "this") 3131 kind = self.sql(expression, "kind") 3132 kind = f" {kind}" if kind else "" 3133 return f"{this}{kind}{path}"
3138 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3139 this = self.sql(expression, "this") 3140 path = self.sql(expression, "path") 3141 path = f", {path}" if path else "" 3142 error_handling = expression.args.get("error_handling") 3143 error_handling = f" {error_handling}" if error_handling else "" 3144 empty_handling = expression.args.get("empty_handling") 3145 empty_handling = f" {empty_handling}" if empty_handling else "" 3146 schema = self.sql(expression, "schema") 3147 return self.func( 3148 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3149 )
3151 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3152 this = self.sql(expression, "this") 3153 kind = self.sql(expression, "kind") 3154 path = self.sql(expression, "path") 3155 path = f" {path}" if path else "" 3156 as_json = " AS JSON" if expression.args.get("as_json") else "" 3157 return f"{this} {kind}{path}{as_json}"
3159 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3160 this = self.sql(expression, "this") 3161 path = self.sql(expression, "path") 3162 path = f", {path}" if path else "" 3163 expressions = self.expressions(expression) 3164 with_ = ( 3165 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3166 if expressions 3167 else "" 3168 ) 3169 return f"OPENJSON({this}{path}){with_}"
3171 def in_sql(self, expression: exp.In) -> str: 3172 query = expression.args.get("query") 3173 unnest = expression.args.get("unnest") 3174 field = expression.args.get("field") 3175 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3176 3177 if query: 3178 in_sql = self.sql(query) 3179 elif unnest: 3180 in_sql = self.in_unnest_op(unnest) 3181 elif field: 3182 in_sql = self.sql(field) 3183 else: 3184 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3185 3186 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3191 def interval_sql(self, expression: exp.Interval) -> str: 3192 unit = self.sql(expression, "unit") 3193 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3194 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3195 unit = f" {unit}" if unit else "" 3196 3197 if self.SINGLE_STRING_INTERVAL: 3198 this = expression.this.name if expression.this else "" 3199 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3200 3201 this = self.sql(expression, "this") 3202 if this: 3203 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3204 this = f" {this}" if unwrapped else f" ({this})" 3205 3206 return f"INTERVAL{this}{unit}"
3211 def reference_sql(self, expression: exp.Reference) -> str: 3212 this = self.sql(expression, "this") 3213 expressions = self.expressions(expression, flat=True) 3214 expressions = f"({expressions})" if expressions else "" 3215 options = self.expressions(expression, key="options", flat=True, sep=" ") 3216 options = f" {options}" if options else "" 3217 return f"REFERENCES {this}{expressions}{options}"
3219 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3220 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3221 parent = expression.parent 3222 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3223 return self.func( 3224 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3225 )
3245 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3246 alias = expression.args["alias"] 3247 3248 parent = expression.parent 3249 pivot = parent and parent.parent 3250 3251 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3252 identifier_alias = isinstance(alias, exp.Identifier) 3253 literal_alias = isinstance(alias, exp.Literal) 3254 3255 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3256 alias.replace(exp.Literal.string(alias.output_name)) 3257 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3258 alias.replace(exp.to_identifier(alias.output_name)) 3259 3260 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3298 def connector_sql( 3299 self, 3300 expression: exp.Connector, 3301 op: str, 3302 stack: t.Optional[t.List[str | exp.Expression]] = None, 3303 ) -> str: 3304 if stack is not None: 3305 if expression.expressions: 3306 stack.append(self.expressions(expression, sep=f" {op} ")) 3307 else: 3308 stack.append(expression.right) 3309 if expression.comments and self.comments: 3310 for comment in expression.comments: 3311 if comment: 3312 op += f" /*{self.sanitize_comment(comment)}*/" 3313 stack.extend((op, expression.left)) 3314 return op 3315 3316 stack = [expression] 3317 sqls: t.List[str] = [] 3318 ops = set() 3319 3320 while stack: 3321 node = stack.pop() 3322 if isinstance(node, exp.Connector): 3323 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3324 else: 3325 sql = self.sql(node) 3326 if sqls and sqls[-1] in ops: 3327 sqls[-1] += f" {sql}" 3328 else: 3329 sqls.append(sql) 3330 3331 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3332 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3352 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3353 format_sql = self.sql(expression, "format") 3354 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3355 to_sql = self.sql(expression, "to") 3356 to_sql = f" {to_sql}" if to_sql else "" 3357 action = self.sql(expression, "action") 3358 action = f" {action}" if action else "" 3359 default = self.sql(expression, "default") 3360 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3361 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3375 def comment_sql(self, expression: exp.Comment) -> str: 3376 this = self.sql(expression, "this") 3377 kind = expression.args["kind"] 3378 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3379 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3380 expression_sql = self.sql(expression, "expression") 3381 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3383 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3384 this = self.sql(expression, "this") 3385 delete = " DELETE" if expression.args.get("delete") else "" 3386 recompress = self.sql(expression, "recompress") 3387 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3388 to_disk = self.sql(expression, "to_disk") 3389 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3390 to_volume = self.sql(expression, "to_volume") 3391 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3392 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3394 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3395 where = self.sql(expression, "where") 3396 group = self.sql(expression, "group") 3397 aggregates = self.expressions(expression, key="aggregates") 3398 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3399 3400 if not (where or group or aggregates) and len(expression.expressions) == 1: 3401 return f"TTL {self.expressions(expression, flat=True)}" 3402 3403 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3420 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3421 this = self.sql(expression, "this") 3422 3423 dtype = self.sql(expression, "dtype") 3424 if dtype: 3425 collate = self.sql(expression, "collate") 3426 collate = f" COLLATE {collate}" if collate else "" 3427 using = self.sql(expression, "using") 3428 using = f" USING {using}" if using else "" 3429 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3430 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3431 3432 default = self.sql(expression, "default") 3433 if default: 3434 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3435 3436 comment = self.sql(expression, "comment") 3437 if comment: 3438 return f"ALTER COLUMN {this} COMMENT {comment}" 3439 3440 visible = expression.args.get("visible") 3441 if visible: 3442 return f"ALTER COLUMN {this} SET {visible}" 3443 3444 allow_null = expression.args.get("allow_null") 3445 drop = expression.args.get("drop") 3446 3447 if not drop and not allow_null: 3448 self.unsupported("Unsupported ALTER COLUMN syntax") 3449 3450 if allow_null is not None: 3451 keyword = "DROP" if drop else "SET" 3452 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3453 3454 return f"ALTER COLUMN {this} DROP DEFAULT"
3470 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3471 compound = " COMPOUND" if expression.args.get("compound") else "" 3472 this = self.sql(expression, "this") 3473 expressions = self.expressions(expression, flat=True) 3474 expressions = f"({expressions})" if expressions else "" 3475 return f"ALTER{compound} SORTKEY {this or expressions}"
3477 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3478 if not self.RENAME_TABLE_WITH_DB: 3479 # Remove db from tables 3480 expression = expression.transform( 3481 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3482 ).assert_is(exp.AlterRename) 3483 this = self.sql(expression, "this") 3484 return f"RENAME TO {this}"
3499 def alter_sql(self, expression: exp.Alter) -> str: 3500 actions = expression.args["actions"] 3501 3502 if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance( 3503 actions[0], exp.ColumnDef 3504 ): 3505 actions_sql = self.expressions(expression, key="actions", flat=True) 3506 actions_sql = f"ADD {actions_sql}" 3507 else: 3508 actions_list = [] 3509 for action in actions: 3510 if isinstance(action, (exp.ColumnDef, exp.Schema)): 3511 action_sql = self.add_column_sql(action) 3512 else: 3513 action_sql = self.sql(action) 3514 if isinstance(action, exp.Query): 3515 action_sql = f"AS {action_sql}" 3516 3517 actions_list.append(action_sql) 3518 3519 actions_sql = self.format_args(*actions_list).lstrip("\n") 3520 3521 exists = " IF EXISTS" if expression.args.get("exists") else "" 3522 on_cluster = self.sql(expression, "cluster") 3523 on_cluster = f" {on_cluster}" if on_cluster else "" 3524 only = " ONLY" if expression.args.get("only") else "" 3525 options = self.expressions(expression, key="options") 3526 options = f", {options}" if options else "" 3527 kind = self.sql(expression, "kind") 3528 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3529 3530 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster}{self.sep()}{actions_sql}{not_valid}{options}"
3532 def add_column_sql(self, expression: exp.Expression) -> str: 3533 sql = self.sql(expression) 3534 if isinstance(expression, exp.Schema): 3535 column_text = " COLUMNS" 3536 elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3537 column_text = " COLUMN" 3538 else: 3539 column_text = "" 3540 3541 return f"ADD{column_text} {sql}"
3555 def distinct_sql(self, expression: exp.Distinct) -> str: 3556 this = self.expressions(expression, flat=True) 3557 3558 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3559 case = exp.case() 3560 for arg in expression.expressions: 3561 case = case.when(arg.is_(exp.null()), exp.null()) 3562 this = self.sql(case.else_(f"({this})")) 3563 3564 this = f" {this}" if this else "" 3565 3566 on = self.sql(expression, "on") 3567 on = f" ON {on}" if on else "" 3568 return f"DISTINCT{this}{on}"
3597 def div_sql(self, expression: exp.Div) -> str: 3598 l, r = expression.left, expression.right 3599 3600 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3601 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3602 3603 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3604 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3605 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3606 3607 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3608 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3609 return self.sql( 3610 exp.cast( 3611 l / r, 3612 to=exp.DataType.Type.BIGINT, 3613 ) 3614 ) 3615 3616 return self.binary(expression, "/")
3712 def log_sql(self, expression: exp.Log) -> str: 3713 this = expression.this 3714 expr = expression.expression 3715 3716 if self.dialect.LOG_BASE_FIRST is False: 3717 this, expr = expr, this 3718 elif self.dialect.LOG_BASE_FIRST is None and expr: 3719 if this.name in ("2", "10"): 3720 return self.func(f"LOG{this.name}", expr) 3721 3722 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3723 3724 return self.func("LOG", this, expr)
3733 def binary(self, expression: exp.Binary, op: str) -> str: 3734 sqls: t.List[str] = [] 3735 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3736 binary_type = type(expression) 3737 3738 while stack: 3739 node = stack.pop() 3740 3741 if type(node) is binary_type: 3742 op_func = node.args.get("operator") 3743 if op_func: 3744 op = f"OPERATOR({self.sql(op_func)})" 3745 3746 stack.append(node.right) 3747 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3748 stack.append(node.left) 3749 else: 3750 sqls.append(self.sql(node)) 3751 3752 return "".join(sqls)
3761 def function_fallback_sql(self, expression: exp.Func) -> str: 3762 args = [] 3763 3764 for key in expression.arg_types: 3765 arg_value = expression.args.get(key) 3766 3767 if isinstance(arg_value, list): 3768 for value in arg_value: 3769 args.append(value) 3770 elif arg_value is not None: 3771 args.append(arg_value) 3772 3773 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3774 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3775 else: 3776 name = expression.sql_name() 3777 3778 return self.func(name, *args)
def
func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
3780 def func( 3781 self, 3782 name: str, 3783 *args: t.Optional[exp.Expression | str], 3784 prefix: str = "(", 3785 suffix: str = ")", 3786 normalize: bool = True, 3787 ) -> str: 3788 name = self.normalize_func(name) if normalize else name 3789 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3791 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3792 arg_sqls = tuple( 3793 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3794 ) 3795 if self.pretty and self.too_wide(arg_sqls): 3796 return self.indent( 3797 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3798 ) 3799 return sep.join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
3804 def format_time( 3805 self, 3806 expression: exp.Expression, 3807 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3808 inverse_time_trie: t.Optional[t.Dict] = None, 3809 ) -> t.Optional[str]: 3810 return format_time( 3811 self.sql(expression, "format"), 3812 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3813 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3814 )
def
expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
3816 def expressions( 3817 self, 3818 expression: t.Optional[exp.Expression] = None, 3819 key: t.Optional[str] = None, 3820 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3821 flat: bool = False, 3822 indent: bool = True, 3823 skip_first: bool = False, 3824 skip_last: bool = False, 3825 sep: str = ", ", 3826 prefix: str = "", 3827 dynamic: bool = False, 3828 new_line: bool = False, 3829 ) -> str: 3830 expressions = expression.args.get(key or "expressions") if expression else sqls 3831 3832 if not expressions: 3833 return "" 3834 3835 if flat: 3836 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3837 3838 num_sqls = len(expressions) 3839 result_sqls = [] 3840 3841 for i, e in enumerate(expressions): 3842 sql = self.sql(e, comment=False) 3843 if not sql: 3844 continue 3845 3846 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3847 3848 if self.pretty: 3849 if self.leading_comma: 3850 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3851 else: 3852 result_sqls.append( 3853 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3854 ) 3855 else: 3856 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3857 3858 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3859 if new_line: 3860 result_sqls.insert(0, "") 3861 result_sqls.append("") 3862 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3863 else: 3864 result_sql = "".join(result_sqls) 3865 3866 return ( 3867 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3868 if indent 3869 else result_sql 3870 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3872 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3873 flat = flat or isinstance(expression.parent, exp.Properties) 3874 expressions_sql = self.expressions(expression, flat=flat) 3875 if flat: 3876 return f"{op} {expressions_sql}" 3877 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3879 def naked_property(self, expression: exp.Property) -> str: 3880 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3881 if not property_name: 3882 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3883 return f"{property_name} {self.sql(expression, 'this')}"
3891 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3892 this = self.sql(expression, "this") 3893 expressions = self.no_identify(self.expressions, expression) 3894 expressions = ( 3895 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3896 ) 3897 return f"{this}{expressions}" if expressions.strip() != "" else this
3907 def when_sql(self, expression: exp.When) -> str: 3908 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3909 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3910 condition = self.sql(expression, "condition") 3911 condition = f" AND {condition}" if condition else "" 3912 3913 then_expression = expression.args.get("then") 3914 if isinstance(then_expression, exp.Insert): 3915 this = self.sql(then_expression, "this") 3916 this = f"INSERT {this}" if this else "INSERT" 3917 then = self.sql(then_expression, "expression") 3918 then = f"{this} VALUES {then}" if then else this 3919 elif isinstance(then_expression, exp.Update): 3920 if isinstance(then_expression.args.get("expressions"), exp.Star): 3921 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3922 else: 3923 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3924 else: 3925 then = self.sql(then_expression) 3926 return f"WHEN {matched}{source}{condition} THEN {then}"
3931 def merge_sql(self, expression: exp.Merge) -> str: 3932 table = expression.this 3933 table_alias = "" 3934 3935 hints = table.args.get("hints") 3936 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3937 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3938 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3939 3940 this = self.sql(table) 3941 using = f"USING {self.sql(expression, 'using')}" 3942 on = f"ON {self.sql(expression, 'on')}" 3943 whens = self.sql(expression, "whens") 3944 3945 returning = self.sql(expression, "returning") 3946 if returning: 3947 whens = f"{whens}{returning}" 3948 3949 sep = self.sep() 3950 3951 return self.prepend_ctes( 3952 expression, 3953 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3954 )
3960 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3961 if not self.SUPPORTS_TO_NUMBER: 3962 self.unsupported("Unsupported TO_NUMBER function") 3963 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3964 3965 fmt = expression.args.get("format") 3966 if not fmt: 3967 self.unsupported("Conversion format is required for TO_NUMBER") 3968 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3969 3970 return self.func("TO_NUMBER", expression.this, fmt)
3972 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3973 this = self.sql(expression, "this") 3974 kind = self.sql(expression, "kind") 3975 settings_sql = self.expressions(expression, key="settings", sep=" ") 3976 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3977 return f"{this}({kind}{args})"
3996 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3997 expressions = self.expressions(expression, flat=True) 3998 expressions = f" {self.wrap(expressions)}" if expressions else "" 3999 buckets = self.sql(expression, "buckets") 4000 kind = self.sql(expression, "kind") 4001 buckets = f" BUCKETS {buckets}" if buckets else "" 4002 order = self.sql(expression, "order") 4003 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
4008 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 4009 expressions = self.expressions(expression, key="expressions", flat=True) 4010 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 4011 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 4012 buckets = self.sql(expression, "buckets") 4013 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
4015 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 4016 this = self.sql(expression, "this") 4017 having = self.sql(expression, "having") 4018 4019 if having: 4020 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 4021 4022 return self.func("ANY_VALUE", this)
4024 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 4025 transform = self.func("TRANSFORM", *expression.expressions) 4026 row_format_before = self.sql(expression, "row_format_before") 4027 row_format_before = f" {row_format_before}" if row_format_before else "" 4028 record_writer = self.sql(expression, "record_writer") 4029 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 4030 using = f" USING {self.sql(expression, 'command_script')}" 4031 schema = self.sql(expression, "schema") 4032 schema = f" AS {schema}" if schema else "" 4033 row_format_after = self.sql(expression, "row_format_after") 4034 row_format_after = f" {row_format_after}" if row_format_after else "" 4035 record_reader = self.sql(expression, "record_reader") 4036 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 4037 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
4039 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 4040 key_block_size = self.sql(expression, "key_block_size") 4041 if key_block_size: 4042 return f"KEY_BLOCK_SIZE = {key_block_size}" 4043 4044 using = self.sql(expression, "using") 4045 if using: 4046 return f"USING {using}" 4047 4048 parser = self.sql(expression, "parser") 4049 if parser: 4050 return f"WITH PARSER {parser}" 4051 4052 comment = self.sql(expression, "comment") 4053 if comment: 4054 return f"COMMENT {comment}" 4055 4056 visible = expression.args.get("visible") 4057 if visible is not None: 4058 return "VISIBLE" if visible else "INVISIBLE" 4059 4060 engine_attr = self.sql(expression, "engine_attr") 4061 if engine_attr: 4062 return f"ENGINE_ATTRIBUTE = {engine_attr}" 4063 4064 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 4065 if secondary_engine_attr: 4066 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 4067 4068 self.unsupported("Unsupported index constraint option.") 4069 return ""
4075 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4076 kind = self.sql(expression, "kind") 4077 kind = f"{kind} INDEX" if kind else "INDEX" 4078 this = self.sql(expression, "this") 4079 this = f" {this}" if this else "" 4080 index_type = self.sql(expression, "index_type") 4081 index_type = f" USING {index_type}" if index_type else "" 4082 expressions = self.expressions(expression, flat=True) 4083 expressions = f" ({expressions})" if expressions else "" 4084 options = self.expressions(expression, key="options", sep=" ") 4085 options = f" {options}" if options else "" 4086 return f"{kind}{this}{index_type}{expressions}{options}"
4088 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4089 if self.NVL2_SUPPORTED: 4090 return self.function_fallback_sql(expression) 4091 4092 case = exp.Case().when( 4093 expression.this.is_(exp.null()).not_(copy=False), 4094 expression.args["true"], 4095 copy=False, 4096 ) 4097 else_cond = expression.args.get("false") 4098 if else_cond: 4099 case.else_(else_cond, copy=False) 4100 4101 return self.sql(case)
4103 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4104 this = self.sql(expression, "this") 4105 expr = self.sql(expression, "expression") 4106 iterator = self.sql(expression, "iterator") 4107 condition = self.sql(expression, "condition") 4108 condition = f" IF {condition}" if condition else "" 4109 return f"{this} FOR {expr} IN {iterator}{condition}"
4117 def predict_sql(self, expression: exp.Predict) -> str: 4118 model = self.sql(expression, "this") 4119 model = f"MODEL {model}" 4120 table = self.sql(expression, "expression") 4121 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4122 parameters = self.sql(expression, "params_struct") 4123 return self.func("PREDICT", model, table, parameters or None)
4135 def toarray_sql(self, expression: exp.ToArray) -> str: 4136 arg = expression.this 4137 if not arg.type: 4138 from sqlglot.optimizer.annotate_types import annotate_types 4139 4140 arg = annotate_types(arg, dialect=self.dialect) 4141 4142 if arg.is_type(exp.DataType.Type.ARRAY): 4143 return self.sql(arg) 4144 4145 cond_for_null = arg.is_(exp.null()) 4146 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4148 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4149 this = expression.this 4150 time_format = self.format_time(expression) 4151 4152 if time_format: 4153 return self.sql( 4154 exp.cast( 4155 exp.StrToTime(this=this, format=expression.args["format"]), 4156 exp.DataType.Type.TIME, 4157 ) 4158 ) 4159 4160 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4161 return self.sql(this) 4162 4163 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4165 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4166 this = expression.this 4167 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4168 return self.sql(this) 4169 4170 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4172 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4173 this = expression.this 4174 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4175 return self.sql(this) 4176 4177 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4179 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4180 this = expression.this 4181 time_format = self.format_time(expression) 4182 4183 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4184 return self.sql( 4185 exp.cast( 4186 exp.StrToTime(this=this, format=expression.args["format"]), 4187 exp.DataType.Type.DATE, 4188 ) 4189 ) 4190 4191 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4192 return self.sql(this) 4193 4194 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4206 def lastday_sql(self, expression: exp.LastDay) -> str: 4207 if self.LAST_DAY_SUPPORTS_DATE_PART: 4208 return self.function_fallback_sql(expression) 4209 4210 unit = expression.text("unit") 4211 if unit and unit != "MONTH": 4212 self.unsupported("Date parts are not supported in LAST_DAY.") 4213 4214 return self.func("LAST_DAY", expression.this)
4223 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4224 if self.CAN_IMPLEMENT_ARRAY_ANY: 4225 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4226 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4227 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4228 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4229 4230 from sqlglot.dialects import Dialect 4231 4232 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4233 if self.dialect.__class__ != Dialect: 4234 self.unsupported("ARRAY_ANY is unsupported") 4235 4236 return self.function_fallback_sql(expression)
4238 def struct_sql(self, expression: exp.Struct) -> str: 4239 expression.set( 4240 "expressions", 4241 [ 4242 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4243 if isinstance(e, exp.PropertyEQ) 4244 else e 4245 for e in expression.expressions 4246 ], 4247 ) 4248 4249 return self.function_fallback_sql(expression)
4257 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4258 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4259 tables = f" {self.expressions(expression)}" 4260 4261 exists = " IF EXISTS" if expression.args.get("exists") else "" 4262 4263 on_cluster = self.sql(expression, "cluster") 4264 on_cluster = f" {on_cluster}" if on_cluster else "" 4265 4266 identity = self.sql(expression, "identity") 4267 identity = f" {identity} IDENTITY" if identity else "" 4268 4269 option = self.sql(expression, "option") 4270 option = f" {option}" if option else "" 4271 4272 partition = self.sql(expression, "partition") 4273 partition = f" {partition}" if partition else "" 4274 4275 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4279 def convert_sql(self, expression: exp.Convert) -> str: 4280 to = expression.this 4281 value = expression.expression 4282 style = expression.args.get("style") 4283 safe = expression.args.get("safe") 4284 strict = expression.args.get("strict") 4285 4286 if not to or not value: 4287 return "" 4288 4289 # Retrieve length of datatype and override to default if not specified 4290 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4291 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4292 4293 transformed: t.Optional[exp.Expression] = None 4294 cast = exp.Cast if strict else exp.TryCast 4295 4296 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4297 if isinstance(style, exp.Literal) and style.is_int: 4298 from sqlglot.dialects.tsql import TSQL 4299 4300 style_value = style.name 4301 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4302 if not converted_style: 4303 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4304 4305 fmt = exp.Literal.string(converted_style) 4306 4307 if to.this == exp.DataType.Type.DATE: 4308 transformed = exp.StrToDate(this=value, format=fmt) 4309 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4310 transformed = exp.StrToTime(this=value, format=fmt) 4311 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4312 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4313 elif to.this == exp.DataType.Type.TEXT: 4314 transformed = exp.TimeToStr(this=value, format=fmt) 4315 4316 if not transformed: 4317 transformed = cast(this=value, to=to, safe=safe) 4318 4319 return self.sql(transformed)
4387 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4388 option = self.sql(expression, "this") 4389 4390 if expression.expressions: 4391 upper = option.upper() 4392 4393 # Snowflake FILE_FORMAT options are separated by whitespace 4394 sep = " " if upper == "FILE_FORMAT" else ", " 4395 4396 # Databricks copy/format options do not set their list of values with EQ 4397 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4398 values = self.expressions(expression, flat=True, sep=sep) 4399 return f"{option}{op}({values})" 4400 4401 value = self.sql(expression, "expression") 4402 4403 if not value: 4404 return option 4405 4406 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4407 4408 return f"{option}{op}{value}"
4410 def credentials_sql(self, expression: exp.Credentials) -> str: 4411 cred_expr = expression.args.get("credentials") 4412 if isinstance(cred_expr, exp.Literal): 4413 # Redshift case: CREDENTIALS <string> 4414 credentials = self.sql(expression, "credentials") 4415 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4416 else: 4417 # Snowflake case: CREDENTIALS = (...) 4418 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4419 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4420 4421 storage = self.sql(expression, "storage") 4422 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4423 4424 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4425 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4426 4427 iam_role = self.sql(expression, "iam_role") 4428 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4429 4430 region = self.sql(expression, "region") 4431 region = f" REGION {region}" if region else "" 4432 4433 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4435 def copy_sql(self, expression: exp.Copy) -> str: 4436 this = self.sql(expression, "this") 4437 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4438 4439 credentials = self.sql(expression, "credentials") 4440 credentials = self.seg(credentials) if credentials else "" 4441 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4442 files = self.expressions(expression, key="files", flat=True) 4443 4444 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4445 params = self.expressions( 4446 expression, 4447 key="params", 4448 sep=sep, 4449 new_line=True, 4450 skip_last=True, 4451 skip_first=True, 4452 indent=self.COPY_PARAMS_ARE_WRAPPED, 4453 ) 4454 4455 if params: 4456 if self.COPY_PARAMS_ARE_WRAPPED: 4457 params = f" WITH ({params})" 4458 elif not self.pretty: 4459 params = f" {params}" 4460 4461 return f"COPY{this}{kind} {files}{credentials}{params}"
4466 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4467 on_sql = "ON" if expression.args.get("on") else "OFF" 4468 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4469 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4470 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4471 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4472 4473 if filter_col or retention_period: 4474 on_sql = self.func("ON", filter_col, retention_period) 4475 4476 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4478 def maskingpolicycolumnconstraint_sql( 4479 self, expression: exp.MaskingPolicyColumnConstraint 4480 ) -> str: 4481 this = self.sql(expression, "this") 4482 expressions = self.expressions(expression, flat=True) 4483 expressions = f" USING ({expressions})" if expressions else "" 4484 return f"MASKING POLICY {this}{expressions}"
4494 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4495 this = self.sql(expression, "this") 4496 expr = expression.expression 4497 4498 if isinstance(expr, exp.Func): 4499 # T-SQL's CLR functions are case sensitive 4500 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4501 else: 4502 expr = self.sql(expression, "expression") 4503 4504 return self.scope_resolution(expr, this)
4512 def rand_sql(self, expression: exp.Rand) -> str: 4513 lower = self.sql(expression, "lower") 4514 upper = self.sql(expression, "upper") 4515 4516 if lower and upper: 4517 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4518 return self.func("RAND", expression.this)
4520 def changes_sql(self, expression: exp.Changes) -> str: 4521 information = self.sql(expression, "information") 4522 information = f"INFORMATION => {information}" 4523 at_before = self.sql(expression, "at_before") 4524 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4525 end = self.sql(expression, "end") 4526 end = f"{self.seg('')}{end}" if end else "" 4527 4528 return f"CHANGES ({information}){at_before}{end}"
4530 def pad_sql(self, expression: exp.Pad) -> str: 4531 prefix = "L" if expression.args.get("is_left") else "R" 4532 4533 fill_pattern = self.sql(expression, "fill_pattern") or None 4534 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4535 fill_pattern = "' '" 4536 4537 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4543 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4544 generate_series = exp.GenerateSeries(**expression.args) 4545 4546 parent = expression.parent 4547 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4548 parent = parent.parent 4549 4550 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4551 return self.sql(exp.Unnest(expressions=[generate_series])) 4552 4553 if isinstance(parent, exp.Select): 4554 self.unsupported("GenerateSeries projection unnesting is not supported.") 4555 4556 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4558 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4559 exprs = expression.expressions 4560 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4561 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4562 else: 4563 rhs = self.expressions(expression) 4564 4565 return self.func(name, expression.this, rhs or None)
4567 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4568 if self.SUPPORTS_CONVERT_TIMEZONE: 4569 return self.function_fallback_sql(expression) 4570 4571 source_tz = expression.args.get("source_tz") 4572 target_tz = expression.args.get("target_tz") 4573 timestamp = expression.args.get("timestamp") 4574 4575 if source_tz and timestamp: 4576 timestamp = exp.AtTimeZone( 4577 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4578 ) 4579 4580 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4581 4582 return self.sql(expr)
4584 def json_sql(self, expression: exp.JSON) -> str: 4585 this = self.sql(expression, "this") 4586 this = f" {this}" if this else "" 4587 4588 _with = expression.args.get("with") 4589 4590 if _with is None: 4591 with_sql = "" 4592 elif not _with: 4593 with_sql = " WITHOUT" 4594 else: 4595 with_sql = " WITH" 4596 4597 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4598 4599 return f"JSON{this}{with_sql}{unique_sql}"
4601 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4602 def _generate_on_options(arg: t.Any) -> str: 4603 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4604 4605 path = self.sql(expression, "path") 4606 returning = self.sql(expression, "returning") 4607 returning = f" RETURNING {returning}" if returning else "" 4608 4609 on_condition = self.sql(expression, "on_condition") 4610 on_condition = f" {on_condition}" if on_condition else "" 4611 4612 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4614 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4615 else_ = "ELSE " if expression.args.get("else_") else "" 4616 condition = self.sql(expression, "expression") 4617 condition = f"WHEN {condition} THEN " if condition else else_ 4618 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4619 return f"{condition}{insert}"
4627 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4628 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4629 empty = expression.args.get("empty") 4630 empty = ( 4631 f"DEFAULT {empty} ON EMPTY" 4632 if isinstance(empty, exp.Expression) 4633 else self.sql(expression, "empty") 4634 ) 4635 4636 error = expression.args.get("error") 4637 error = ( 4638 f"DEFAULT {error} ON ERROR" 4639 if isinstance(error, exp.Expression) 4640 else self.sql(expression, "error") 4641 ) 4642 4643 if error and empty: 4644 error = ( 4645 f"{empty} {error}" 4646 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4647 else f"{error} {empty}" 4648 ) 4649 empty = "" 4650 4651 null = self.sql(expression, "null") 4652 4653 return f"{empty}{error}{null}"
4659 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4660 this = self.sql(expression, "this") 4661 path = self.sql(expression, "path") 4662 4663 passing = self.expressions(expression, "passing") 4664 passing = f" PASSING {passing}" if passing else "" 4665 4666 on_condition = self.sql(expression, "on_condition") 4667 on_condition = f" {on_condition}" if on_condition else "" 4668 4669 path = f"{path}{passing}{on_condition}" 4670 4671 return self.func("JSON_EXISTS", this, path)
4673 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4674 array_agg = self.function_fallback_sql(expression) 4675 4676 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4677 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4678 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4679 parent = expression.parent 4680 if isinstance(parent, exp.Filter): 4681 parent_cond = parent.expression.this 4682 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4683 else: 4684 this = expression.this 4685 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4686 if this.find(exp.Column): 4687 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4688 this_sql = ( 4689 self.expressions(this) 4690 if isinstance(this, exp.Distinct) 4691 else self.sql(expression, "this") 4692 ) 4693 4694 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4695 4696 return array_agg
4704 def grant_sql(self, expression: exp.Grant) -> str: 4705 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4706 4707 kind = self.sql(expression, "kind") 4708 kind = f" {kind}" if kind else "" 4709 4710 securable = self.sql(expression, "securable") 4711 securable = f" {securable}" if securable else "" 4712 4713 principals = self.expressions(expression, key="principals", flat=True) 4714 4715 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4716 4717 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4741 def overlay_sql(self, expression: exp.Overlay): 4742 this = self.sql(expression, "this") 4743 expr = self.sql(expression, "expression") 4744 from_sql = self.sql(expression, "from") 4745 for_sql = self.sql(expression, "for") 4746 for_sql = f" FOR {for_sql}" if for_sql else "" 4747 4748 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4754 def string_sql(self, expression: exp.String) -> str: 4755 this = expression.this 4756 zone = expression.args.get("zone") 4757 4758 if zone: 4759 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4760 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4761 # set for source_tz to transpile the time conversion before the STRING cast 4762 this = exp.ConvertTimezone( 4763 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4764 ) 4765 4766 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4776 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4777 filler = self.sql(expression, "this") 4778 filler = f" {filler}" if filler else "" 4779 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4780 return f"TRUNCATE{filler} {with_count}"
4782 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4783 if self.SUPPORTS_UNIX_SECONDS: 4784 return self.function_fallback_sql(expression) 4785 4786 start_ts = exp.cast( 4787 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4788 ) 4789 4790 return self.sql( 4791 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4792 )
4794 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4795 dim = expression.expression 4796 4797 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4798 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4799 if not (dim.is_int and dim.name == "1"): 4800 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4801 dim = None 4802 4803 # If dimension is required but not specified, default initialize it 4804 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4805 dim = exp.Literal.number(1) 4806 4807 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4809 def attach_sql(self, expression: exp.Attach) -> str: 4810 this = self.sql(expression, "this") 4811 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4812 expressions = self.expressions(expression) 4813 expressions = f" ({expressions})" if expressions else "" 4814 4815 return f"ATTACH{exists_sql} {this}{expressions}"
4817 def detach_sql(self, expression: exp.Detach) -> str: 4818 this = self.sql(expression, "this") 4819 # the DATABASE keyword is required if IF EXISTS is set 4820 # without it, DuckDB throws an error: Parser Error: syntax error at or near "exists" (Line Number: 1) 4821 # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax 4822 exists_sql = " DATABASE IF EXISTS" if expression.args.get("exists") else "" 4823 4824 return f"DETACH{exists_sql} {this}"
4832 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4833 this_sql = self.sql(expression, "this") 4834 if isinstance(expression.this, exp.Table): 4835 this_sql = f"TABLE {this_sql}" 4836 4837 return self.func( 4838 "FEATURES_AT_TIME", 4839 this_sql, 4840 expression.args.get("time"), 4841 expression.args.get("num_rows"), 4842 expression.args.get("ignore_feature_nulls"), 4843 )
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4850 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4851 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4852 encode = f"{encode} {self.sql(expression, 'this')}" 4853 4854 properties = expression.args.get("properties") 4855 if properties: 4856 encode = f"{encode} {self.properties(properties)}" 4857 4858 return encode
4860 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4861 this = self.sql(expression, "this") 4862 include = f"INCLUDE {this}" 4863 4864 column_def = self.sql(expression, "column_def") 4865 if column_def: 4866 include = f"{include} {column_def}" 4867 4868 alias = self.sql(expression, "alias") 4869 if alias: 4870 include = f"{include} AS {alias}" 4871 4872 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4884 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4885 partitions = self.expressions(expression, "partition_expressions") 4886 create = self.expressions(expression, "create_expressions") 4887 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4889 def partitionbyrangepropertydynamic_sql( 4890 self, expression: exp.PartitionByRangePropertyDynamic 4891 ) -> str: 4892 start = self.sql(expression, "start") 4893 end = self.sql(expression, "end") 4894 4895 every = expression.args["every"] 4896 if isinstance(every, exp.Interval) and every.this.is_string: 4897 every.this.replace(exp.Literal.number(every.name)) 4898 4899 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4912 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4913 kind = self.sql(expression, "kind") 4914 option = self.sql(expression, "option") 4915 option = f" {option}" if option else "" 4916 this = self.sql(expression, "this") 4917 this = f" {this}" if this else "" 4918 columns = self.expressions(expression) 4919 columns = f" {columns}" if columns else "" 4920 return f"{kind}{option} STATISTICS{this}{columns}"
4922 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4923 this = self.sql(expression, "this") 4924 columns = self.expressions(expression) 4925 inner_expression = self.sql(expression, "expression") 4926 inner_expression = f" {inner_expression}" if inner_expression else "" 4927 update_options = self.sql(expression, "update_options") 4928 update_options = f" {update_options} UPDATE" if update_options else "" 4929 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4940 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4941 kind = self.sql(expression, "kind") 4942 this = self.sql(expression, "this") 4943 this = f" {this}" if this else "" 4944 inner_expression = self.sql(expression, "expression") 4945 return f"VALIDATE {kind}{this}{inner_expression}"
4947 def analyze_sql(self, expression: exp.Analyze) -> str: 4948 options = self.expressions(expression, key="options", sep=" ") 4949 options = f" {options}" if options else "" 4950 kind = self.sql(expression, "kind") 4951 kind = f" {kind}" if kind else "" 4952 this = self.sql(expression, "this") 4953 this = f" {this}" if this else "" 4954 mode = self.sql(expression, "mode") 4955 mode = f" {mode}" if mode else "" 4956 properties = self.sql(expression, "properties") 4957 properties = f" {properties}" if properties else "" 4958 partition = self.sql(expression, "partition") 4959 partition = f" {partition}" if partition else "" 4960 inner_expression = self.sql(expression, "expression") 4961 inner_expression = f" {inner_expression}" if inner_expression else "" 4962 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4964 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4965 this = self.sql(expression, "this") 4966 namespaces = self.expressions(expression, key="namespaces") 4967 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4968 passing = self.expressions(expression, key="passing") 4969 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4970 columns = self.expressions(expression, key="columns") 4971 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4972 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4973 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4979 def export_sql(self, expression: exp.Export) -> str: 4980 this = self.sql(expression, "this") 4981 connection = self.sql(expression, "connection") 4982 connection = f"WITH CONNECTION {connection} " if connection else "" 4983 options = self.sql(expression, "options") 4984 return f"EXPORT DATA {connection}{options} AS {this}"
4989 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4990 variable = self.sql(expression, "this") 4991 default = self.sql(expression, "default") 4992 default = f" = {default}" if default else "" 4993 4994 kind = self.sql(expression, "kind") 4995 if isinstance(expression.args.get("kind"), exp.Schema): 4996 kind = f"TABLE {kind}" 4997 4998 return f"{variable} AS {kind}{default}"
5000 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 5001 kind = self.sql(expression, "kind") 5002 this = self.sql(expression, "this") 5003 set = self.sql(expression, "expression") 5004 using = self.sql(expression, "using") 5005 using = f" USING {using}" if using else "" 5006 5007 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 5008 5009 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
5028 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 5029 # Snowflake GET/PUT statements: 5030 # PUT <file> <internalStage> <properties> 5031 # GET <internalStage> <file> <properties> 5032 props = expression.args.get("properties") 5033 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 5034 this = self.sql(expression, "this") 5035 target = self.sql(expression, "target") 5036 5037 if isinstance(expression, exp.Put): 5038 return f"PUT {this} {target}{props_sql}" 5039 else: 5040 return f"GET {target} {this}{props_sql}"
5048 def decodecase_sql(self, expression: exp.DecodeCase) -> str: 5049 if self.SUPPORTS_DECODE_CASE: 5050 return self.func("DECODE", *expression.expressions) 5051 5052 expression, *expressions = expression.expressions 5053 5054 ifs = [] 5055 for search, result in zip(expressions[::2], expressions[1::2]): 5056 if isinstance(search, exp.Literal): 5057 ifs.append(exp.If(this=expression.eq(search), true=result)) 5058 elif isinstance(search, exp.Null): 5059 ifs.append(exp.If(this=expression.is_(exp.Null()), true=result)) 5060 else: 5061 if isinstance(search, exp.Binary): 5062 search = exp.paren(search) 5063 5064 cond = exp.or_( 5065 expression.eq(search), 5066 exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False), 5067 copy=False, 5068 ) 5069 ifs.append(exp.If(this=cond, true=result)) 5070 5071 case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None) 5072 return self.sql(case)
5074 def semanticview_sql(self, expression: exp.SemanticView) -> str: 5075 this = self.sql(expression, "this") 5076 this = self.seg(this, sep="") 5077 dimensions = self.expressions( 5078 expression, "dimensions", dynamic=True, skip_first=True, skip_last=True 5079 ) 5080 dimensions = self.seg(f"DIMENSIONS {dimensions}") if dimensions else "" 5081 metrics = self.expressions( 5082 expression, "metrics", dynamic=True, skip_first=True, skip_last=True 5083 ) 5084 metrics = self.seg(f"METRICS {metrics}") if metrics else "" 5085 where = self.sql(expression, "where") 5086 where = self.seg(f"WHERE {where}") if where else "" 5087 return f"SEMANTIC_VIEW({self.indent(this + metrics + dimensions + where)}{self.seg(')', sep='')}"